From 769721300f6e939c68128fc8a3cb7c3a5b1bebca Mon Sep 17 00:00:00 2001 From: Tename <37943518+Tename@users.noreply.github.com> Date: Tue, 29 Dec 2020 22:12:38 -0800 Subject: [PATCH 01/21] boggers --- code/game/objects/items/robot/robot_items.dm | 118 ++++++++++++------ .../objects/items/robot/robot_upgrades.dm | 29 +---- .../mob/living/silicon/robot/robot_modules.dm | 1 + .../designs/mechfabricator_designs.dm | 9 -- .../mob/living/silicon/robot/robot_modules.dm | 11 ++ 5 files changed, 96 insertions(+), 72 deletions(-) diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 5d8ccc02..a2be7aea 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -900,46 +900,90 @@ RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_iconb) update_icon() -//////////////////// -//engi part holder// -//////////////////// +/********************************************************************** + Grippers oh god oh fuck +***********************************************************************/ -/obj/item/borg/apparatus/circuit - name = "circuit manipulation apparatus" - desc = "A special apparatus for carrying and manipulating circuit boards. Alt-Z or right-click to drop the stored object." - icon_state = "borg_hardware_apparatus" - storable = list(/obj/item/circuitboard, - /obj/item/electronics) +/obj/item/weapon/gripper + name = "engineering gripper" + desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more. Alt click to drop instead of use." + icon = 'icons/obj/device.dmi' + icon_state = "gripper" -/obj/item/borg/apparatus/circuit/Initialize() - . = ..() - update_icon() + item_flags = NOBLUDGEON -/obj/item/borg/apparatus/circuit/update_icon() - cut_overlays() - if(stored) - COMPILE_OVERLAYS(stored) - stored.pixel_x = -3 - stored.pixel_y = 0 - var/image/arm - if(istype(stored, /obj/item/circuitboard)) - arm = image("icon"="borg_hardware_apparatus_arm1", "layer"=FLOAT_LAYER) + //Has a list of items that it can hold. + var/list/can_hold = list( + /obj/item/circuitboard, + /obj/item/light, + /obj/item/electronics, + /obj/item/tank, + /obj/item/wallframe, + /obj/item/vending_refill, + /obj/item/stack/sheet, + /obj/item/stack/tile, + /obj/item/stack/rods, + /obj/item/stock_parts + ) + //Basically a blacklist for any subtypes above we dont want + var/list/cannot_hold = list( + /obj/item/stack/sheet/mineral/plasma, + /obj/item/stack/sheet/plasteel + ) + + var/obj/item/wrapped = null // Item currently being held. + +//Used to interact with UI's of held items, such as gas tanks and airlock electronics. +/obj/item/weapon/gripper/AltClick(mob/user) + if(wrapped) + wrapped.forceMove(get_turf(wrapped)) + to_chat(user, "You drop the [wrapped].") + wrapped = null + return ..() + +/obj/item/weapon/gripper/pre_attack(var/atom/target, var/mob/living/silicon/robot/user, proximity, params) + + if(!proximity) + return + + if(!wrapped) + for(var/obj/item/thing in src.contents) + wrapped = thing + break + + if(wrapped) //Already have an item. + //Temporary put wrapped into user so target's attackby() checks pass. + wrapped.loc = user + + //Pass the attack on to the target. This might delete/relocate wrapped. + var/resolved = target.attackby(wrapped,user) + if(!resolved && wrapped && target) + wrapped.afterattack(target,user,1) + //If wrapped was neither deleted nor put into target, put it back into the gripper. + if(wrapped && user && (wrapped.loc == user)) + wrapped.loc = src else - arm = image("icon"="borg_hardware_apparatus_arm2", "layer"=FLOAT_LAYER) - var/image/img = image("icon"=stored, "layer"=FLOAT_LAYER) - img.plane = FLOAT_PLANE - add_overlay(arm) - add_overlay(img) - else - var/image/arm = image("icon"="borg_hardware_apparatus_arm1", "layer"=FLOAT_LAYER) - add_overlay(arm) + wrapped = null + return -/obj/item/borg/apparatus/circuit/examine() - . = ..() - if(stored) - . += "The apparatus currently has [stored] secured." + else if(istype(target,/obj/item)) + var/obj/item/I = target + var/grab = 0 -/obj/item/borg/apparatus/circuit/pre_attack(atom/A, mob/living/user, params) - . = ..() - if(istype(A, /obj/item/aiModule) && !stored) //If an admin wants a borg to upload laws, who am I to stop them? Otherwise, we can hint that it fails - to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.") + for(var/typepath in can_hold) + if(istype(I,typepath)) + grab = 1 + for(var/badpath in cannot_hold) + if(istype(I,badpath)) + if(!user.emagged) + grab = 0 + continue + + //We can grab the item, finally. + if(grab) + to_chat(user, "You collect \the [I].") + I.loc = src + wrapped = I + return + else + to_chat(user, "Your gripper cannot hold \the [target].") diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 5e7081c1..a2f0d6f4 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -489,7 +489,9 @@ out procedures" icon_state = "cyborg_upgrade3" require_module = 1 - module_type = /obj/item/robot_module/medical + module_type = list( + /obj/item/robot_module/medical, + /obj/item/robot_module/medihound) /obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) . = ..() @@ -620,31 +622,6 @@ if (RPED) R.module.remove_module(RPED, TRUE) -/obj/item/borg/upgrade/circuit_app - name = "circuit manipulation apparatus" - desc = "An engineering cyborg upgrade allowing for manipulation of circuit boards." - icon_state = "cyborg_upgrade3" - require_module = TRUE - module_type = list(/obj/item/robot_module/engineering) - -/obj/item/borg/upgrade/circuit_app/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules - if(C) - to_chat(user, "This unit is already equipped with a circuit apparatus!") - return FALSE - - C = new(R.module) - R.module.basic_modules += C - R.module.add_module(C, FALSE, TRUE) - -/obj/item/borg/upgrade/circuit_app/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules - if (C) - R.module.remove_module(C, TRUE) /obj/item/borg/upgrade/pinpointer name = "medical cyborg crew pinpointer" diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 9c98417a..f3ee17a4 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -329,6 +329,7 @@ /obj/item/stack/sheet/rglass/cyborg, /obj/item/stack/rods/cyborg, /obj/item/stack/tile/plasteel/cyborg, + /obj/item/weapon/gripper, /obj/item/stack/cable_coil/cyborg) emag_modules = list(/obj/item/borg/stun) ratvar_modules = list( diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm index 4659d322..902dc62d 100644 --- a/code/modules/research/designs/mechfabricator_designs.dm +++ b/code/modules/research/designs/mechfabricator_designs.dm @@ -768,15 +768,6 @@ construction_time = 120 category = list("Cyborg Upgrade Modules") -/datum/design/borg_upgrade_circuit_app - name = "Cyborg Upgrade (Circuit Manipulator)" - id = "borg_upgrade_circuitapp" - build_type = MECHFAB - build_path = /obj/item/borg/upgrade/circuit_app - materials = list(MAT_METAL=2000, MAT_TITANIUM=500) - construction_time = 120 - category = list("Cyborg Upgrade Modules") - /datum/design/borg_upgrade_pinpointer name = "Cyborg Upgrade (Crew pinpointer)" id = "borg_upgrade_pinpointer" diff --git a/modular_citadel/code/modules/mob/living/silicon/robot/robot_modules.dm b/modular_citadel/code/modules/mob/living/silicon/robot/robot_modules.dm index a466c152..a2ce17a3 100644 --- a/modular_citadel/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/modular_citadel/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -43,6 +43,7 @@ /obj/item/robot_module/k9 name = "Security K-9 Unit" basic_modules = list( + /obj/item/assembly/flash/cyborg, /obj/item/restraints/handcuffs/cable/zipties, /obj/item/storage/bag/borgdelivery, /obj/item/dogborg/jaws/big, @@ -106,6 +107,7 @@ /obj/item/robot_module/medihound name = "MediHound" basic_modules = list( + /obj/item/assembly/flash/cyborg, /obj/item/dogborg/jaws/small, /obj/item/storage/bag/borgdelivery, /obj/item/analyzer/nose, @@ -116,6 +118,13 @@ /obj/item/roller/robo, /obj/item/crowbar/cyborg, /obj/item/borg/apparatus/beaker, + /obj/item/surgical_drapes, + /obj/item/retractor, + /obj/item/hemostat, + /obj/item/cautery, + /obj/item/surgicaldrill, + /obj/item/scalpel, + /obj/item/circular_saw, /obj/item/reagent_containers/borghypo, /obj/item/twohanded/shockpaddles/cyborg/hound, /obj/item/stack/medical/gauze/cyborg, @@ -172,6 +181,7 @@ /obj/item/robot_module/scrubpup name = "Scrub Pup" basic_modules = list( + /obj/item/assembly/flash/cyborg, /obj/item/dogborg/jaws/small, /obj/item/analyzer/nose, /obj/item/crowbar/cyborg, @@ -225,6 +235,7 @@ /obj/item/robot_module/borgi name = "Borgi" basic_modules = list( + /obj/item/assembly/flash/cyborg, /obj/item/dogborg/jaws/small, /obj/item/storage/bag/borgdelivery, /obj/item/analyzer/nose, From a30508ed3febbfd4e13a5f324dbf04e4d6aa1d3a Mon Sep 17 00:00:00 2001 From: Tename <37943518+Tename@users.noreply.github.com> Date: Tue, 29 Dec 2020 22:29:20 -0800 Subject: [PATCH 02/21] burgers --- code/game/objects/items/robot/robot_upgrades.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index a2f0d6f4..7b35f5a4 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -489,9 +489,7 @@ out procedures" icon_state = "cyborg_upgrade3" require_module = 1 - module_type = list( - /obj/item/robot_module/medical, - /obj/item/robot_module/medihound) + module_type = /obj/item/robot_module/medical /obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) . = ..() From 6cfeecf166348efa4d9063cb38ebdb76ca728862 Mon Sep 17 00:00:00 2001 From: QuoteFox <49098813+quotefox@users.noreply.github.com> Date: Tue, 5 Jan 2021 10:27:44 +0000 Subject: [PATCH 03/21] tg food pixel art tg food pixel art --- icons/obj/food/food.dmi | Bin 72079 -> 73174 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index a42b95f1ea6d5ad61619ee8f4b523634b038d5df..b6a62e8e61e72784aa3802c0cd706d18e2e614bc 100644 GIT binary patch literal 73174 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

r!(O@iED6sXg#wUVv_J|$dIJ5c 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 Date: Tue, 5 Jan 2021 13:20:54 +0000 Subject: [PATCH 04/21] Food icons, and some custom ones Food icons, (mainly ports from Eris, they do amazing work over there with their sprite work), and some custom ones I made myself with similar colour pallets. Also makes synth meat less attractive. --- .../food_and_drinks/food/snacks/meat.dm | 13 ++++++++++++- icons/obj/food/burgerbread.dmi | Bin 35939 -> 35934 bytes icons/obj/food/food.dmi | Bin 73174 -> 73765 bytes icons/obj/food/food_ingredients.dmi | Bin 3539 -> 3243 bytes 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/code/modules/food_and_drinks/food/snacks/meat.dm b/code/modules/food_and_drinks/food/snacks/meat.dm index a62ab42d..2e340838 100644 --- a/code/modules/food_and_drinks/food/snacks/meat.dm +++ b/code/modules/food_and_drinks/food/snacks/meat.dm @@ -84,6 +84,7 @@ cooked_type = /obj/item/reagent_containers/food/snacks/meat/steak/chicken slice_path = /obj/item/reagent_containers/food/snacks/meat/rawcutlet/chicken tastes = list("chicken" = 1) + icon_state = "bird" /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime icon_state = "slimemeat" @@ -165,8 +166,10 @@ /obj/item/reagent_containers/food/snacks/meat/slab/synthmeat name = "synthmeat" - desc = "A synthetic slab of meat." + desc = "A synthetic chunk of meat." + icon_state = "meatproduct" //growing meat will make it look like a lumpo foodtype = RAW | MEAT //hurr durr chemicals we're harmed in the production of this meat thus its non-vegan. + cooked_type = /obj/item/reagent_containers/food/snacks/meat/steak/meatproduct /obj/item/reagent_containers/food/snacks/meat/slab/meatproduct name = "meat product" @@ -255,6 +258,7 @@ bitesize = 4 tastes = list("meat" = 1, "wheat" = 1) foodtype = GRAIN + cooked_type = /obj/item/reagent_containers/food/snacks/meat/steak/meatproduct /obj/item/reagent_containers/food/snacks/meat/slab/gorilla name = "gorilla meat" @@ -309,10 +313,15 @@ /obj/item/reagent_containers/food/snacks/meat/steak/chicken name = "chicken steak" //Can you have chicken steaks? Maybe this should be renamed once it gets new sprites. tastes = list("chicken" = 1) + icon_state = "bird_steak" /obj/item/reagent_containers/food/snacks/meat/steak/plain foodtype = MEAT +/obj/item/reagent_containers/food/snacks/meat/steak/meatproduct + foodtype = MEAT + icon_state = "meatproduct_steak" + /obj/item/reagent_containers/food/snacks/meat/steak/plain/human tastes = list("tender meat" = 1) foodtype = MEAT | GROSS @@ -391,6 +400,7 @@ name = "raw chicken cutlet" cooked_type = /obj/item/reagent_containers/food/snacks/meat/cutlet/chicken tastes = list("chicken" = 1) + icon_state = "bird_cutlet" /obj/item/reagent_containers/food/snacks/meat/rawcutlet/killertomato name = "raw killer tomato cutlet" @@ -465,3 +475,4 @@ /obj/item/reagent_containers/food/snacks/meat/cutlet/chicken name = "chicken cutlet" tastes = list("chicken" = 1) + icon_state = "bird_cutlet_cooked" \ No newline at end of file diff --git a/icons/obj/food/burgerbread.dmi b/icons/obj/food/burgerbread.dmi index 3b13f8a00464c2646ab2e24602dcd08f80d7beee..a5c3e7df249620c1ecf66f5236742178653cf1f3 100644 GIT binary patch literal 35934 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 N1fH&bF6*2UngFGoz-Ryf literal 35939 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 diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index b6a62e8e61e72784aa3802c0cd706d18e2e614bc..ba0bfeec32747f2e822f4c98c36f2c12c4596267 100644 GIT binary patch literal 73765 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

j6xzbb6EL= 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

r!(O@iED6sXg#wUVv_J|$dIJ5c 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 B>x%#Y delta 3352 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 From 4b8b03fea035a2d3fa6b132246272ca915c8e1a5 Mon Sep 17 00:00:00 2001 From: BlackMajor Date: Mon, 25 Nov 2019 12:31:15 +1300 Subject: [PATCH 05/21] Ports new Brain Traumas from /tg/. Does not include the shitty creeper antag or Wizard Traumas, i'm porting wizard traumas later. Also adds the Desynchronizer and changes the way some heaRing args are handled. --- code/__DEFINES/components.dm | 208 +- code/__DEFINES/status_effects.dm | 3 + code/__DEFINES/traits.dm | 3 + code/__HELPERS/mobs.dm | 1198 ++++++------ code/_globalvars/lists/poll_ignore.dm | 4 + code/_onclick/hud/alert.dm | 6 + code/datums/brain_damage/brain_trauma.dm | 25 +- code/datums/brain_damage/hypnosis.dm | 17 +- code/datums/brain_damage/imaginary_friend.dm | 70 +- code/datums/brain_damage/mild.dm | 164 +- code/datums/brain_damage/phobia.dm | 119 +- code/datums/brain_damage/severe.dm | 6 +- code/datums/brain_damage/special.dm | 109 +- code/datums/brain_damage/split_personality.dm | 16 +- code/datums/components/nanites.dm | 6 - code/datums/components/stationloving.dm | 3 +- code/datums/status_effects/debuffs.dm | 97 +- code/datums/status_effects/neutral.dm | 14 + code/datums/status_effects/status_effect.dm | 9 + code/game/atoms.dm | 1740 ++++++++--------- .../temporary_visuals/miscellaneous.dm | 5 + code/game/say.dm | 2 +- .../antagonists/traitor/datum_traitor.dm | 6 + code/modules/client/client_colour.dm | 3 + code/modules/mob/living/carbon/say.dm | 16 +- code/modules/mob/living/emote.dm | 1038 +++++----- .../chemistry/reagents/alcohol_reagents.dm | 1 + .../chemistry/reagents/drink_reagents.dm | 4 + .../research/designs/bluespace_designs.dm | 162 +- .../research/nanites/nanite_programs.dm | 3 - .../nanites/nanite_programs/sensor.dm | 17 +- code/modules/research/techweb/all_nodes.dm | 9 + icons/mob/screen_alert.dmi | Bin 105669 -> 107438 bytes icons/obj/device.dmi | Bin 53647 -> 54198 bytes tgstation.dme | 1 + 35 files changed, 2740 insertions(+), 2344 deletions(-) diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 1e2ba19d..e2ef92ed 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -1,104 +1,104 @@ -#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) ) - -#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) ) - -#define COMPONENT_INCOMPATIBLE 1 -#define COMPONENT_NOTRANSFER 2 - -// How multiple components of the exact same type are handled in the same datum - -#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default) -#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed -#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted -#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new - -// All signals. Format: -// When the signal is called: (signal arguments) -// All signals send the source datum of the signal as the first argument - -// global signals -// These are signals which can be listened to by any component on any parent -// start global signals with "!", this used to be necessary but now it's just a formatting choice -////////////////////////////////////////////////////////////////// - -// /datum signals -// /atom signals - //Positions for overrides list - //End positions -#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): () -///////////////// - -// /area signals - -// /turf signals - -// /atom/movable signals - -// /mob/living signals - -// /mob/living/carbon signals - -// /obj signals -#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag) - -// /obj/item signals - -// /obj/item/clothing signals - -// /obj/item/implant signals - //This uses all return values of COMSIG_IMPLANT_OTHER - -// /obj/item/pda signals - -// /obj/item/radio signals - -// /obj/item/pen signals - -// /mob/living/carbon/human signals - -// /datum/species signals - -/*******Component Specific Signals*******/ -//Janitor - -//Food - -//Mood -#define COMSIG_INCREASE_SANITY "decrease_sanity" //Called when you want to increase sanity from anywhere in the code. -#define COMSIG_DECREASE_SANITY "increase_sanity" //Same as above but to decrease sanity instead. - -//NTnet - -//Nanites - -// /datum/component/storage signals - -// /datum/action signals - -/*******Non-Signal Component Related Defines*******/ - -//Redirection component init flags -#define REDIRECT_TRANSFER_WITH_TURF 1 - -//Arch -#define ARCH_PROB "probability" //Probability for each item -#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount - -//Ouch my toes! -#define CALTROP_BYPASS_SHOES 1 -#define CALTROP_IGNORE_WALKERS 2 - -#define ELEMENT_INCOMPATIBLE 1 // Return value to cancel attaching - -// /datum/element flags -/// Causes the detach proc to be called when the host object is being deleted -#define ELEMENT_DETACH (1 << 0) -/** - * Only elements created with the same arguments given after `id_arg_index` share an element instance - * The arguments are the same when the text and number values are the same and all other values have the same ref - */ -#define ELEMENT_BESPOKE (1 << 1) -#define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size" //Called when the movable's glide size is updated: (new_glide_size) - -#define COMSIG_LIVING_FULLY_HEAL "living_fully_healed" //from base of /mob/living/fully_heal(): (admin_revive) - +#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) ) + +#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) ) + +#define COMPONENT_INCOMPATIBLE 1 +#define COMPONENT_NOTRANSFER 2 + +// How multiple components of the exact same type are handled in the same datum + +#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default) +#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed +#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted +#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new + +// All signals. Format: +// When the signal is called: (signal arguments) +// All signals send the source datum of the signal as the first argument + +// global signals +// These are signals which can be listened to by any component on any parent +// start global signals with "!", this used to be necessary but now it's just a formatting choice +////////////////////////////////////////////////////////////////// + +// /datum signals +// /atom signals + //Positions for overrides list + //End positions +#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): () +///////////////// + +// /area signals + +// /turf signals + +// /atom/movable signals + +// /mob/living signals + +// /mob/living/carbon signals + +// /obj signals +#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag) + +// /obj/item signals + +// /obj/item/clothing signals + +// /obj/item/implant signals + //This uses all return values of COMSIG_IMPLANT_OTHER + +// /obj/item/pda signals + +// /obj/item/radio signals + +// /obj/item/pen signals + +// /mob/living/carbon/human signals + +// /datum/species signals + +/*******Component Specific Signals*******/ +//Janitor + +//Food + +//Mood +#define COMSIG_INCREASE_SANITY "decrease_sanity" //Called when you want to increase sanity from anywhere in the code. +#define COMSIG_DECREASE_SANITY "increase_sanity" //Same as above but to decrease sanity instead. + +//NTnet + +//Nanites + +// /datum/component/storage signals + +// /datum/action signals + +/*******Non-Signal Component Related Defines*******/ + +//Redirection component init flags +#define REDIRECT_TRANSFER_WITH_TURF 1 + +//Arch +#define ARCH_PROB "probability" //Probability for each item +#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount + +//Ouch my toes! +#define CALTROP_BYPASS_SHOES 1 +#define CALTROP_IGNORE_WALKERS 2 + +#define ELEMENT_INCOMPATIBLE 1 // Return value to cancel attaching + +// /datum/element flags +/// Causes the detach proc to be called when the host object is being deleted +#define ELEMENT_DETACH (1 << 0) +/** + * Only elements created with the same arguments given after `id_arg_index` share an element instance + * The arguments are the same when the text and number values are the same and all other values have the same ref + */ +#define ELEMENT_BESPOKE (1 << 1) +#define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size" //Called when the movable's glide size is updated: (new_glide_size) + +#define COMSIG_LIVING_FULLY_HEAL "living_fully_healed" //from base of /mob/living/fully_heal(): (admin_revive) + diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 664ebf8b..1ab4fd3f 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -7,6 +7,8 @@ #define STATUS_EFFECT_REPLACE 2 //if it allows only one, but new instances replace +#define STATUS_EFFECT_REFRESH 3 // if it only allows one, and new instances just instead refresh the timer + /////////// // BUFFS // /////////// @@ -75,6 +77,7 @@ #define STATUS_EFFECT_ICHORIAL_STAIN /datum/status_effect/ichorial_stain //Prevents a servant from being revived by vitality matrices for one minute. #define STATUS_EFFECT_STASIS /datum/status_effect/incapacitating/stasis //Halts biological functions like bleeding, chemical processing, blood regeneration, walking, etc +#define STATUS_EFFECT_SPASMS /datum/status_effect/spasms //causes random muscle spasms #define STATUS_EFFECT_NO_COMBAT_MODE /datum/status_effect/no_combat_mode //Wont allow combat mode and will disable it #define STATUS_EFFECT_MESMERIZE /datum/status_effect/no_combat_mode/mesmerize //Just reskinned no_combat_mode diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 91d7f5a8..3bfb2da0 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -112,6 +112,7 @@ #define TRAIT_NOHARDCRIT "nohardcrit" #define TRAIT_NOSOFTCRIT "nosoftcrit" #define TRAIT_MINDSHIELD "mindshield" +#define TRAIT_SIXTHSENSE "sixthsense" #define TRAIT_DISSECTED "dissected" #define TRAIT_FEARLESS "fearless" #define TRAIT_UNSTABLE "unstable" @@ -121,6 +122,7 @@ #define TRAIT_PARALYSIS_R_LEG "para-r-leg" #define TRAIT_DISK_VERIFIER "disk-verifier" #define TRAIT_UNINTELLIGIBLE_SPEECH "unintelligible-speech" +#define TRAIT_SOOTHED_THROAT "soothed-throat" #define TRAIT_LAW_ENFORCEMENT_METABOLISM "law-enforcement-metabolism" #define TRAIT_STRONG_GRABBER "strong_grabber" #define TRAIT_CALCIUM_HEALER "calcium_healer" @@ -206,6 +208,7 @@ #define CULT_TRAIT "cult" #define CURSED_ITEM_TRAIT "cursed-item" // The item is magically cursed #define ABSTRACT_ITEM_TRAIT "abstract-item" +#define STATUS_EFFECT_TRAIT "status-effect" #define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention // unique trait sources, still defines diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 49ffe578..854fa01e 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -1,598 +1,600 @@ -/proc/random_blood_type() - return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") - -/proc/random_eye_color() - switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino")) - if("brown") - return "630" - if("hazel") - return "542" - if("grey") - return pick("666","777","888","999","aaa","bbb","ccc") - if("blue") - return "36c" - if("green") - return "060" - if("amber") - return "fc0" - if("albino") - return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9") - else - return "000" - -#define UNDIE_COLORABLE(U) (U?.has_color) - -/proc/random_underwear(gender) - if(!GLOB.underwear_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/bottom, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - switch(gender) - if(MALE) - return pick(GLOB.underwear_m) - if(FEMALE) - return pick(GLOB.underwear_f) - else - return pick(GLOB.underwear_list) - -/proc/random_undershirt(gender) - if(!GLOB.undershirt_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/top, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - switch(gender) - if(MALE) - return pick(GLOB.undershirt_m) - if(FEMALE) - return pick(GLOB.undershirt_f) - else - return pick(GLOB.undershirt_list) - -/proc/random_socks() - if(!GLOB.socks_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/socks, GLOB.socks_list) - return pick(GLOB.socks_list) - -/proc/random_features(intendedspecies) - if(!GLOB.tails_list_human.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) - if(!GLOB.tails_list_lizard.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) - if(!GLOB.snouts_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - if(!GLOB.horns_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) - if(!GLOB.ears_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) - if(!GLOB.frills_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - if(!GLOB.spines_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - if(!GLOB.legs_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - if(!GLOB.body_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - if(!GLOB.wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - if(!GLOB.deco_wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list) - if(!GLOB.moth_wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - if(!GLOB.moth_fluffs_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluffs_list) - if(!GLOB.moth_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) - - //CIT CHANGES - genitals and such - if(!GLOB.cock_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list) - if(!GLOB.balls_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list) - if(!GLOB.vagina_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list) - if(!GLOB.breasts_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list) - if(!GLOB.ipc_screens_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list) - if(!GLOB.ipc_antennas_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list) - if(!GLOB.mam_body_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) - if(!GLOB.mam_tails_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) - if(!GLOB.mam_ears_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) - if(!GLOB.mam_snouts_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list) - - //snowflake check so people's ckey features don't get randomly put on unmonkeys/spawns - var/list/snowflake_mam_tails_list = list() - for(var/mtpath in GLOB.mam_tails_list) - var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[mtpath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) - continue - if(!S.ckeys_allowed) - snowflake_mam_tails_list[S.name] = mtpath - var/list/snowflake_markings_list = list() - for(var/mmpath in GLOB.mam_body_markings_list) - var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[mmpath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) - continue - if(!S.ckeys_allowed) - snowflake_markings_list[S.name] = mmpath - var/list/snowflake_ears_list = list() - for(var/mepath in GLOB.mam_ears_list) - var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[mepath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) - continue - if(!S.ckeys_allowed) - snowflake_ears_list[S.name] = mepath - var/list/snowflake_mam_snouts_list = list() - for(var/mspath in GLOB.mam_snouts_list) - var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[mspath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(!S.ckeys_allowed) - snowflake_mam_snouts_list[S.name] = mspath - var/list/snowflake_ipc_antenna_list = list() - for(var/mspath in GLOB.ipc_antennas_list) - var/datum/sprite_accessory/mam_snouts/instance = GLOB.ipc_antennas_list[mspath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) - continue - if(!S.ckeys_allowed) - snowflake_ipc_antenna_list[S.name] = mspath - var/color1 = random_short_color() - var/color2 = random_short_color() - var/color3 = random_short_color() - - //CIT CHANGE - changes this entire return to support cit's snowflake parts - return(list( - "mcolor" = color1, - "mcolor2" = color2, - "mcolor3" = color3, - "tail_lizard" = pick(GLOB.tails_list_lizard), - "tail_human" = "None", - "wings" = "None", - "snout" = pick(GLOB.snouts_list), - "horns" = pick(GLOB.horns_list), - "ears" = "None", - "frills" = pick(GLOB.frills_list), - "spines" = pick(GLOB.spines_list), - "body_markings" = pick(GLOB.body_markings_list), - "legs" = pick("Normal Legs","Digitigrade Legs"), - "caps" = pick(GLOB.caps_list), - "moth_wings" = pick(GLOB.moth_wings_list), - "moth_markings" = pick(GLOB.moth_markings_list), - "insect_fluff" = "None", - "taur" = "None", - "mam_body_markings" = snowflake_markings_list.len ? pick(snowflake_markings_list) : "None", - "mam_ears" = snowflake_ears_list ? pick(snowflake_ears_list) : "None", - "mam_snouts" = snowflake_mam_snouts_list ? pick(snowflake_mam_snouts_list) : "None", - "mam_tail" = snowflake_mam_tails_list ? pick(snowflake_mam_tails_list) : "None", - "mam_tail_animated" = "None", - "xenodorsal" = "Standard", - "xenohead" = "Standard", - "xenotail" = "Xenomorph Tail", - "exhibitionist" = FALSE, - "genitals_use_skintone" = FALSE, - "has_cock" = FALSE, - "cock_shape" = pick(GLOB.cock_shapes_list), - "cock_length" = 6, - "cock_girth_ratio" = COCK_GIRTH_RATIO_DEF, - "cock_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "has_sheath" = FALSE, - "sheath_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "has_balls" = FALSE, - "balls_internal" = FALSE, - "balls_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "balls_amount" = 2, - "balls_sack_size" = BALLS_SACK_SIZE_DEF, - "balls_size" = BALLS_SIZE_DEF, - "balls_shape" = "Single", - "balls_cum_rate" = CUM_RATE, - "balls_cum_mult" = CUM_RATE_MULT, - "balls_efficiency" = CUM_EFFICIENCY, - "balls_fluid" = /datum/reagent/consumable/semen, - "has_ovi" = FALSE, - "ovi_shape" = "knotted", - "ovi_length" = 6, - "ovi_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "has_eggsack" = FALSE, - "eggsack_internal" = TRUE, - "eggsack_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "eggsack_size" = BALLS_SACK_SIZE_DEF, - "eggsack_egg_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "eggsack_egg_size" = EGG_GIRTH_DEF, - "has_breasts" = FALSE, - "breasts_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "breasts_size" = pick(GLOB.breasts_size_list), - "breasts_shape" = "Pair", - "breasts_fluid" = /datum/reagent/consumable/milk, - "breasts_producing" = FALSE, - "has_vag" = FALSE, - "vag_shape" = pick(GLOB.vagina_shapes_list), - "vag_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "vag_clits" = 1, - "vag_clit_diam" = 0.25, - "vag_clit_len" = 0.25, - "has_womb" = FALSE, - "can_get_preg" = FALSE, - "womb_cum_rate" = CUM_RATE, - "womb_cum_mult" = CUM_RATE_MULT, - "womb_efficiency" = CUM_EFFICIENCY, - "womb_fluid" = "femcum", - "ipc_screen" = snowflake_ipc_antenna_list ? pick(snowflake_ipc_antenna_list) : "None", - "ipc_antenna" = "None", - "flavor_text" = "")) - -/proc/random_hair_style(gender) - switch(gender) - if(MALE) - return pick(GLOB.hair_styles_male_list) - if(FEMALE) - return pick(GLOB.hair_styles_female_list) - else - return pick(GLOB.hair_styles_list) - -/proc/random_facial_hair_style(gender) - switch(gender) - if(MALE) - return pick(GLOB.facial_hair_styles_male_list) - if(FEMALE) - return pick(GLOB.facial_hair_styles_female_list) - else - return pick(GLOB.facial_hair_styles_list) - -/proc/random_unique_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - if(gender==FEMALE) - . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) - else - . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) - - if(!findname(.)) - break - -/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(lizard_name(gender)) - - if(!findname(.)) - break - -/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(plasmaman_name()) - - if(!findname(.)) - break - -/proc/random_unique_moth_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) - - if(!findname(.)) - break - -/proc/random_skin_tone() - return pick(GLOB.skin_tones) - -GLOBAL_LIST_INIT(skin_tones, list( - "albino", - "caucasian1", - "caucasian2", - "caucasian3", - "latino", - "mediterranean", - "asian1", - "asian2", - "arab", - "indian", - "african1", - "african2" - )) - -GLOBAL_LIST_EMPTY(species_list) - -/proc/age2agedescription(age) - switch(age) - if(0 to 1) - return "infant" - if(1 to 3) - return "toddler" - if(3 to 13) - return "child" - if(13 to 19) - return "teenager" - if(19 to 30) - return "young adult" - if(30 to 45) - return "adult" - if(45 to 60) - return "middle-aged" - if(60 to 70) - return "aging" - if(70 to INFINITY) - return "elderly" - else - return "unknown" - -/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0) - if(!user || !target) - return 0 - var/user_loc = user.loc - - var/drifting = 0 - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = 1 - - var/target_loc = target.loc - - var/holding = user.get_active_held_item() - var/datum/progressbar/progbar - if (progress) - progbar = new(user, time, target) - - var/endtime = world.time+time - var/starttime = world.time - . = 1 - while (world.time < endtime) - stoplag(1) - if (progress) - progbar.update(world.time - starttime) - if(QDELETED(user) || QDELETED(target)) - . = 0 - break - if(uninterruptible) - continue - - if(drifting && !user.inertia_dir) - drifting = 0 - user_loc = user.loc - - if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke())) - . = 0 - break - if (progress) - qdel(progbar) - - -//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action -/mob/proc/break_do_after_checks(list/checked_health, check_clicks) - if(check_clicks && next_move > world.time) - return FALSE - return TRUE - -//pass a list in the format list("health" = mob's health var) to check health during this -/mob/living/break_do_after_checks(list/checked_health, check_clicks) - if(islist(checked_health)) - if(health < checked_health["health"]) - return FALSE - checked_health["health"] = health - return ..() - -/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null) - if(!user) - return 0 - var/atom/Tloc = null - if(target && !isturf(target)) - Tloc = target.loc - - var/atom/Uloc = user.loc - - var/drifting = 0 - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = 1 - - var/holding = user.get_active_held_item() - - var/holdingnull = 1 //User's hand started out empty, check for an empty hand - if(holding) - holdingnull = 0 //Users hand started holding something, check to see if it's still holding that - - delay *= user.do_after_coefficent() - - var/datum/progressbar/progbar - if (progress) - progbar = new(user, delay, target) - - var/endtime = world.time + delay - var/starttime = world.time - . = 1 - while (world.time < endtime) - stoplag(1) - if (progress) - progbar.update(world.time - starttime) - - if(drifting && !user.inertia_dir) - drifting = 0 - Uloc = user.loc - - if(QDELETED(user) || user.stat || user.IsKnockdown() || user.IsStun() || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) - . = 0 - break - - if(isliving(user)) - var/mob/living/L = user - if(L.recoveringstam) - . = 0 - break - - if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) - if((Uloc != Tloc || Tloc != user) && !drifting) - . = 0 - break - - if(needhand) - //This might seem like an odd check, but you can still need a hand even when it's empty - //i.e the hand is used to pull some item/tool out of the construction - if(!holdingnull) - if(!holding) - . = 0 - break - if(user.get_active_held_item() != holding) - . = 0 - break - if (progress) - qdel(progbar) - -/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 - . = 1 - return - -/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks) - if(!user || !targets) - return 0 - if(!islist(targets)) - targets = list(targets) - var/user_loc = user.loc - - var/drifting = 0 - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = 1 - - var/list/originalloc = list() - for(var/atom/target in targets) - originalloc[target] = target.loc - - var/holding = user.get_active_held_item() - var/datum/progressbar/progbar - if(progress) - progbar = new(user, time, targets[1]) - - var/endtime = world.time + time - var/starttime = world.time - . = 1 - mainloop: - while(world.time < endtime) - stoplag(1) - if(progress) - progbar.update(world.time - starttime) - if(QDELETED(user) || !targets) - . = 0 - break - if(uninterruptible) - continue - - if(drifting && !user.inertia_dir) - drifting = 0 - user_loc = user.loc - - for(var/atom/target in targets) - if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke())) - . = 0 - break mainloop - if(progbar) - qdel(progbar) - -/proc/is_species(A, species_datum) - . = FALSE - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.dna && istype(H.dna.species, species_datum)) - . = TRUE - -/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args) - var/turf/T = get_turf(target) - if(!T) - CRASH("attempt to spawn atom type: [spawn_type] in nullspace") - - var/list/new_args = list(T) - if(extra_args) - new_args += extra_args - - for(var/j in 1 to amount) - var/atom/X = new spawn_type(arglist(new_args)) - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - -/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) - var/turf/T = get_turf(target) - var/step_count = 0 - if(!T) - CRASH("attempt to spawn atom type: [spawn_type] in nullspace") - - for(var/j in 1 to amount) - var/atom/movable/X = new spawn_type(T) - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - - if(always_max_walk || prob(walk_chance)) - if(always_max_walk) - step_count = max_walk - else - step_count = rand(1, max_walk) - - for(var/i in 1 to step_count) - step(X, pick(NORTH, SOUTH, EAST, WEST)) - -/proc/deadchat_broadcast(message, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) - message = "[message]" - for(var/mob/M in GLOB.player_list) - var/datum/preferences/prefs - if(M.client && M.client.prefs) - prefs = M.client.prefs - else - prefs = new - - var/adminoverride = 0 - if(M.client && M.client.holder && (prefs.chat_toggles & CHAT_DEAD)) - adminoverride = 1 - if(isnewplayer(M) && !adminoverride) - continue - if(M.stat != DEAD && !adminoverride) - continue - if(speaker_key && speaker_key in prefs.ignoring) - continue - - switch(message_type) - if(DEADCHAT_DEATHRATTLE) - if(prefs.toggles & DISABLE_DEATHRATTLE) - continue - if(DEADCHAT_ARRIVALRATTLE) - if(prefs.toggles & DISABLE_ARRIVALRATTLE) - continue - - if(isobserver(M)) - var/rendered_message = message - - if(follow_target) - var/F - if(turf_target) - F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target) - else - F = FOLLOW_LINK(M, follow_target) - rendered_message = "[F] [message]" - else if(turf_target) - var/turf_link = TURF_LINK(M, turf_target) - rendered_message = "[turf_link] [message]" - - to_chat(M, rendered_message) - else - to_chat(M, message) - -//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. -/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) - var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs - var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs - - if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0) - for(var/T in typesof(/mob/living/simple_animal)) - var/mob/living/simple_animal/SA = T - switch(initial(SA.gold_core_spawnable)) - if(HOSTILE_SPAWN) - mob_spawn_meancritters += T - if(FRIENDLY_SPAWN) - mob_spawn_nicecritters += T - - var/chosen - if(mob_class == FRIENDLY_SPAWN) - chosen = pick(mob_spawn_nicecritters) - else - chosen = pick(mob_spawn_meancritters) - var/mob/living/simple_animal/C = new chosen(spawn_location) - return C +/proc/random_blood_type() + return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") + +/proc/random_eye_color() + switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino")) + if("brown") + return "630" + if("hazel") + return "542" + if("grey") + return pick("666","777","888","999","aaa","bbb","ccc") + if("blue") + return "36c" + if("green") + return "060" + if("amber") + return "fc0" + if("albino") + return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9") + else + return "000" + +#define UNDIE_COLORABLE(U) (U?.has_color) + +/proc/random_underwear(gender) + if(!GLOB.underwear_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/bottom, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + switch(gender) + if(MALE) + return pick(GLOB.underwear_m) + if(FEMALE) + return pick(GLOB.underwear_f) + else + return pick(GLOB.underwear_list) + +/proc/random_undershirt(gender) + if(!GLOB.undershirt_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/top, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + switch(gender) + if(MALE) + return pick(GLOB.undershirt_m) + if(FEMALE) + return pick(GLOB.undershirt_f) + else + return pick(GLOB.undershirt_list) + +/proc/random_socks() + if(!GLOB.socks_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/socks, GLOB.socks_list) + return pick(GLOB.socks_list) + +/proc/random_features(intendedspecies) + if(!GLOB.tails_list_human.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) + if(!GLOB.tails_list_lizard.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) + if(!GLOB.snouts_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) + if(!GLOB.horns_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) + if(!GLOB.ears_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) + if(!GLOB.frills_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) + if(!GLOB.spines_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) + if(!GLOB.legs_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) + if(!GLOB.body_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) + if(!GLOB.wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) + if(!GLOB.deco_wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list) + if(!GLOB.moth_wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) + if(!GLOB.moth_fluffs_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluffs_list) + if(!GLOB.moth_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) + + //CIT CHANGES - genitals and such + if(!GLOB.cock_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list) + if(!GLOB.balls_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list) + if(!GLOB.vagina_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list) + if(!GLOB.breasts_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list) + if(!GLOB.ipc_screens_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list) + if(!GLOB.ipc_antennas_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list) + if(!GLOB.mam_body_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) + if(!GLOB.mam_tails_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) + if(!GLOB.mam_ears_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) + if(!GLOB.mam_snouts_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list) + + //snowflake check so people's ckey features don't get randomly put on unmonkeys/spawns + var/list/snowflake_mam_tails_list = list() + for(var/mtpath in GLOB.mam_tails_list) + var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[mtpath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) + continue + if(!S.ckeys_allowed) + snowflake_mam_tails_list[S.name] = mtpath + var/list/snowflake_markings_list = list() + for(var/mmpath in GLOB.mam_body_markings_list) + var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[mmpath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) + continue + if(!S.ckeys_allowed) + snowflake_markings_list[S.name] = mmpath + var/list/snowflake_ears_list = list() + for(var/mepath in GLOB.mam_ears_list) + var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[mepath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) + continue + if(!S.ckeys_allowed) + snowflake_ears_list[S.name] = mepath + var/list/snowflake_mam_snouts_list = list() + for(var/mspath in GLOB.mam_snouts_list) + var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[mspath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(!S.ckeys_allowed) + snowflake_mam_snouts_list[S.name] = mspath + var/list/snowflake_ipc_antenna_list = list() + for(var/mspath in GLOB.ipc_antennas_list) + var/datum/sprite_accessory/mam_snouts/instance = GLOB.ipc_antennas_list[mspath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies)) + continue + if(!S.ckeys_allowed) + snowflake_ipc_antenna_list[S.name] = mspath + var/color1 = random_short_color() + var/color2 = random_short_color() + var/color3 = random_short_color() + + //CIT CHANGE - changes this entire return to support cit's snowflake parts + return(list( + "mcolor" = color1, + "mcolor2" = color2, + "mcolor3" = color3, + "tail_lizard" = pick(GLOB.tails_list_lizard), + "tail_human" = "None", + "wings" = "None", + "snout" = pick(GLOB.snouts_list), + "horns" = pick(GLOB.horns_list), + "ears" = "None", + "frills" = pick(GLOB.frills_list), + "spines" = pick(GLOB.spines_list), + "body_markings" = pick(GLOB.body_markings_list), + "legs" = pick("Normal Legs","Digitigrade Legs"), + "caps" = pick(GLOB.caps_list), + "moth_wings" = pick(GLOB.moth_wings_list), + "moth_markings" = pick(GLOB.moth_markings_list), + "insect_fluff" = "None", + "taur" = "None", + "mam_body_markings" = snowflake_markings_list.len ? pick(snowflake_markings_list) : "None", + "mam_ears" = snowflake_ears_list ? pick(snowflake_ears_list) : "None", + "mam_snouts" = snowflake_mam_snouts_list ? pick(snowflake_mam_snouts_list) : "None", + "mam_tail" = snowflake_mam_tails_list ? pick(snowflake_mam_tails_list) : "None", + "mam_tail_animated" = "None", + "xenodorsal" = "Standard", + "xenohead" = "Standard", + "xenotail" = "Xenomorph Tail", + "exhibitionist" = FALSE, + "genitals_use_skintone" = FALSE, + "has_cock" = FALSE, + "cock_shape" = pick(GLOB.cock_shapes_list), + "cock_length" = 6, + "cock_girth_ratio" = COCK_GIRTH_RATIO_DEF, + "cock_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "has_sheath" = FALSE, + "sheath_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "has_balls" = FALSE, + "balls_internal" = FALSE, + "balls_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "balls_amount" = 2, + "balls_sack_size" = BALLS_SACK_SIZE_DEF, + "balls_size" = BALLS_SIZE_DEF, + "balls_shape" = "Single", + "balls_cum_rate" = CUM_RATE, + "balls_cum_mult" = CUM_RATE_MULT, + "balls_efficiency" = CUM_EFFICIENCY, + "balls_fluid" = /datum/reagent/consumable/semen, + "has_ovi" = FALSE, + "ovi_shape" = "knotted", + "ovi_length" = 6, + "ovi_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "has_eggsack" = FALSE, + "eggsack_internal" = TRUE, + "eggsack_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "eggsack_size" = BALLS_SACK_SIZE_DEF, + "eggsack_egg_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "eggsack_egg_size" = EGG_GIRTH_DEF, + "has_breasts" = FALSE, + "breasts_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "breasts_size" = pick(GLOB.breasts_size_list), + "breasts_shape" = "Pair", + "breasts_fluid" = /datum/reagent/consumable/milk, + "breasts_producing" = FALSE, + "has_vag" = FALSE, + "vag_shape" = pick(GLOB.vagina_shapes_list), + "vag_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "vag_clits" = 1, + "vag_clit_diam" = 0.25, + "vag_clit_len" = 0.25, + "has_womb" = FALSE, + "can_get_preg" = FALSE, + "womb_cum_rate" = CUM_RATE, + "womb_cum_mult" = CUM_RATE_MULT, + "womb_efficiency" = CUM_EFFICIENCY, + "womb_fluid" = "femcum", + "ipc_screen" = snowflake_ipc_antenna_list ? pick(snowflake_ipc_antenna_list) : "None", + "ipc_antenna" = "None", + "flavor_text" = "")) + +/proc/random_hair_style(gender) + switch(gender) + if(MALE) + return pick(GLOB.hair_styles_male_list) + if(FEMALE) + return pick(GLOB.hair_styles_female_list) + else + return pick(GLOB.hair_styles_list) + +/proc/random_facial_hair_style(gender) + switch(gender) + if(MALE) + return pick(GLOB.facial_hair_styles_male_list) + if(FEMALE) + return pick(GLOB.facial_hair_styles_female_list) + else + return pick(GLOB.facial_hair_styles_list) + +/proc/random_unique_name(gender, attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + if(gender==FEMALE) + . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) + else + . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + + if(!findname(.)) + break + +/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(lizard_name(gender)) + + if(!findname(.)) + break + +/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(plasmaman_name()) + + if(!findname(.)) + break + +/proc/random_unique_moth_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) + + if(!findname(.)) + break + +/proc/random_skin_tone() + return pick(GLOB.skin_tones) + +GLOBAL_LIST_INIT(skin_tones, list( + "albino", + "caucasian1", + "caucasian2", + "caucasian3", + "latino", + "mediterranean", + "asian1", + "asian2", + "arab", + "indian", + "african1", + "african2" + )) + +GLOBAL_LIST_EMPTY(species_list) + +/proc/age2agedescription(age) + switch(age) + if(0 to 1) + return "infant" + if(1 to 3) + return "toddler" + if(3 to 13) + return "child" + if(13 to 19) + return "teenager" + if(19 to 30) + return "young adult" + if(30 to 45) + return "adult" + if(45 to 60) + return "middle-aged" + if(60 to 70) + return "aging" + if(70 to INFINITY) + return "elderly" + else + return "unknown" + +/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0) + if(!user || !target) + return 0 + var/user_loc = user.loc + + var/drifting = 0 + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = 1 + + var/target_loc = target.loc + + var/holding = user.get_active_held_item() + var/datum/progressbar/progbar + if (progress) + progbar = new(user, time, target) + + var/endtime = world.time+time + var/starttime = world.time + . = 1 + while (world.time < endtime) + stoplag(1) + if (progress) + progbar.update(world.time - starttime) + if(QDELETED(user) || QDELETED(target)) + . = 0 + break + if(uninterruptible) + continue + + if(drifting && !user.inertia_dir) + drifting = 0 + user_loc = user.loc + + if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke())) + . = 0 + break + if (progress) + qdel(progbar) + + +//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action +/mob/proc/break_do_after_checks(list/checked_health, check_clicks) + if(check_clicks && next_move > world.time) + return FALSE + return TRUE + +//pass a list in the format list("health" = mob's health var) to check health during this +/mob/living/break_do_after_checks(list/checked_health, check_clicks) + if(islist(checked_health)) + if(health < checked_health["health"]) + return FALSE + checked_health["health"] = health + return ..() + +/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null) + if(!user) + return 0 + var/atom/Tloc = null + if(target && !isturf(target)) + Tloc = target.loc + + var/atom/Uloc = user.loc + + var/drifting = 0 + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = 1 + + var/holding = user.get_active_held_item() + + var/holdingnull = 1 //User's hand started out empty, check for an empty hand + if(holding) + holdingnull = 0 //Users hand started holding something, check to see if it's still holding that + + delay *= user.do_after_coefficent() + + var/datum/progressbar/progbar + if (progress) + progbar = new(user, delay, target) + + var/endtime = world.time + delay + var/starttime = world.time + . = 1 + while (world.time < endtime) + stoplag(1) + if (progress) + progbar.update(world.time - starttime) + + if(drifting && !user.inertia_dir) + drifting = 0 + Uloc = user.loc + + if(QDELETED(user) || user.stat || user.IsKnockdown() || user.IsStun() || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) + . = 0 + break + + if(isliving(user)) + var/mob/living/L = user + if(L.recoveringstam) + . = 0 + break + + if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) + if((Uloc != Tloc || Tloc != user) && !drifting) + . = 0 + break + + if(needhand) + //This might seem like an odd check, but you can still need a hand even when it's empty + //i.e the hand is used to pull some item/tool out of the construction + if(!holdingnull) + if(!holding) + . = 0 + break + if(user.get_active_held_item() != holding) + . = 0 + break + if (progress) + qdel(progbar) + +/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 + . = 1 + return + +/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks) + if(!user || !targets) + return 0 + if(!islist(targets)) + targets = list(targets) + var/user_loc = user.loc + + var/drifting = 0 + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = 1 + + var/list/originalloc = list() + for(var/atom/target in targets) + originalloc[target] = target.loc + + var/holding = user.get_active_held_item() + var/datum/progressbar/progbar + if(progress) + progbar = new(user, time, targets[1]) + + var/endtime = world.time + time + var/starttime = world.time + . = 1 + mainloop: + while(world.time < endtime) + stoplag(1) + if(progress) + progbar.update(world.time - starttime) + if(QDELETED(user) || !targets) + . = 0 + break + if(uninterruptible) + continue + + if(drifting && !user.inertia_dir) + drifting = 0 + user_loc = user.loc + + for(var/atom/target in targets) + if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke())) + . = 0 + break mainloop + if(progbar) + qdel(progbar) + +/proc/is_species(A, species_datum) + . = FALSE + if(ishuman(A)) + var/mob/living/carbon/human/H = A + if(H.dna && istype(H.dna.species, species_datum)) + . = TRUE + +/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args) + var/turf/T = get_turf(target) + if(!T) + CRASH("attempt to spawn atom type: [spawn_type] in nullspace") + + var/list/new_args = list(T) + if(extra_args) + new_args += extra_args + + for(var/j in 1 to amount) + var/atom/X = new spawn_type(arglist(new_args)) + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + +/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) + var/turf/T = get_turf(target) + var/step_count = 0 + if(!T) + CRASH("attempt to spawn atom type: [spawn_type] in nullspace") + + for(var/j in 1 to amount) + var/atom/movable/X = new spawn_type(T) + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + + if(always_max_walk || prob(walk_chance)) + if(always_max_walk) + step_count = max_walk + else + step_count = rand(1, max_walk) + + for(var/i in 1 to step_count) + step(X, pick(NORTH, SOUTH, EAST, WEST)) + +/proc/deadchat_broadcast(message, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) + message = "[message]" + for(var/mob/M in GLOB.player_list) + var/datum/preferences/prefs + if(M.client && M.client.prefs) + prefs = M.client.prefs + else + prefs = new + + var/override = FALSE + if(M.client && M.client.holder && (prefs.chat_toggles & CHAT_DEAD)) + override = TRUE + if(HAS_TRAIT(M, TRAIT_SIXTHSENSE)) + override = TRUE + if(isnewplayer(M) && !override) + continue + if(M.stat != DEAD && !override) + continue + if(speaker_key && speaker_key in prefs.ignoring) + continue + + switch(message_type) + if(DEADCHAT_DEATHRATTLE) + if(prefs.toggles & DISABLE_DEATHRATTLE) + continue + if(DEADCHAT_ARRIVALRATTLE) + if(prefs.toggles & DISABLE_ARRIVALRATTLE) + continue + + if(isobserver(M)) + var/rendered_message = message + + if(follow_target) + var/F + if(turf_target) + F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target) + else + F = FOLLOW_LINK(M, follow_target) + rendered_message = "[F] [message]" + else if(turf_target) + var/turf_link = TURF_LINK(M, turf_target) + rendered_message = "[turf_link] [message]" + + to_chat(M, rendered_message) + else + to_chat(M, message) + +//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. +/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) + var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs + var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs + + if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0) + for(var/T in typesof(/mob/living/simple_animal)) + var/mob/living/simple_animal/SA = T + switch(initial(SA.gold_core_spawnable)) + if(HOSTILE_SPAWN) + mob_spawn_meancritters += T + if(FRIENDLY_SPAWN) + mob_spawn_nicecritters += T + + var/chosen + if(mob_class == FRIENDLY_SPAWN) + chosen = pick(mob_spawn_nicecritters) + else + chosen = pick(mob_spawn_meancritters) + var/mob/living/simple_animal/C = new chosen(spawn_location) + return C diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm index b28ee975..fea595ab 100644 --- a/code/_globalvars/lists/poll_ignore.dm +++ b/code/_globalvars/lists/poll_ignore.dm @@ -13,6 +13,8 @@ #define POLL_IGNORE_GOLEM "golem" #define POLL_IGNORE_SWARMER "swarmer" #define POLL_IGNORE_DRONE "drone" +#define POLL_IGNORE_IMAGINARYFRIEND "imaginary_friend" +#define POLL_IGNORE_SPLITPERSONALITY "split_personality" #define POLL_IGNORE_DEMON "demon" #define POLL_IGNORE_WIZARD "wizard" @@ -30,6 +32,8 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list( POLL_IGNORE_GOLEM = "Golems", POLL_IGNORE_SWARMER = "Swarmer shells", POLL_IGNORE_DRONE = "Drone shells", + POLL_IGNORE_IMAGINARYFRIEND = "Imaginary Friend", + POLL_IGNORE_SPLITPERSONALITY = "Split Personality", POLL_IGNORE_DEMON = "Demons", POLL_IGNORE_WIZARD = "Wizards", )) diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index afbe3218..7c135d7b 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -213,6 +213,12 @@ or something covering your eyes." desc = "Whoa man, you're tripping balls! Careful you don't get addicted... if you aren't already." icon_state = "high" +/obj/screen/alert/hypnosis + name = "Hypnosis" + desc = "Something's hypnotizing you, but you're not really sure about what." + icon_state = "hypnosis" + var/phrase + /obj/screen/alert/drunk //Not implemented name = "Drunk" desc = "All that alcohol you've been drinking is impairing your speech, motor skills, and mental cognition. Make sure to act like it." diff --git a/code/datums/brain_damage/brain_trauma.dm b/code/datums/brain_damage/brain_trauma.dm index 56a3f396..1aa1341c 100644 --- a/code/datums/brain_damage/brain_trauma.dm +++ b/code/datums/brain_damage/brain_trauma.dm @@ -1,10 +1,12 @@ //Brain Traumas are the new actual brain damage. Brain damage itself acts as a way to acquire traumas: every time brain damage is dealt, there's a chance of receiving a trauma. //This chance gets higher the higher the mob's brainloss is. Removing traumas is a separate thing from removing brain damage: you can get restored to full brain operativity, -//but keep the quirks, until repaired by mannitol (for mild/special ones) or brain surgery (for severe ones). +// but keep the quirks, until repaired by neurine, surgery, lobotomy or magic; depending on the resilience +// of the trauma. + /datum/brain_trauma var/name = "Brain Trauma" var/desc = "A trauma caused by brain damage, which causes issues to the patient." - var/scan_desc = "a generic brain trauma" //description when detected by a health scanner + var/scan_desc = "generic brain trauma" //description when detected by a health scanner var/mob/living/carbon/owner //the poor bastard var/obj/item/organ/brain/brain //the poor bastard's brain var/gain_text = "You feel traumatized." @@ -12,15 +14,21 @@ var/can_gain = TRUE var/random_gain = TRUE //can this be gained through random traumas? var/resilience = TRAUMA_RESILIENCE_BASIC //how hard is this to cure? + var/clonable = TRUE // will this transfer if the brain is cloned? /datum/brain_trauma/Destroy() - brain.traumas -= src + if(brain && brain.traumas) + brain.traumas -= src if(owner) on_lose() brain = null owner = null return ..() +/datum/brain_trauma/proc/on_clone() + if(clonable) + return new type + //Called on life ticks /datum/brain_trauma/proc/on_life() return @@ -33,17 +41,24 @@ /datum/brain_trauma/proc/on_gain() to_chat(owner, gain_text) RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech) + RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/handle_hearing) //Called when removed from a mob /datum/brain_trauma/proc/on_lose(silent) if(!silent) to_chat(owner, lose_text) UnregisterSignal(owner, COMSIG_MOB_SAY) + UnregisterSignal(owner, COMSIG_MOVABLE_HEAR) //Called when hearing a spoken message -/datum/brain_trauma/proc/on_hear(message, speaker, message_language, raw_message, radio_freq) - return message +/datum/brain_trauma/proc/handle_hearing(datum/source, list/hearing_args) + UnregisterSignal(owner, COMSIG_MOVABLE_HEAR) //Called when speaking /datum/brain_trauma/proc/handle_speech(datum/source, list/speech_args) UnregisterSignal(owner, COMSIG_MOB_SAY) + + +//Called when hugging. expand into generally interacting, where future coders could switch the intent? +/datum/brain_trauma/proc/on_hug(mob/living/hugger, mob/living/hugged) + return diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm index 8909d1b8..f937c196 100644 --- a/code/datums/brain_damage/hypnosis.dm +++ b/code/datums/brain_damage/hypnosis.dm @@ -5,6 +5,7 @@ gain_text = "" lose_text = "" resilience = TRAUMA_RESILIENCE_SURGERY + var/hypnotic_phrase = "" var/regex/target_phrase @@ -44,18 +45,17 @@ "You feel a part of your mind repeating this over and over. You need to follow these words.",\ "Something about this sounds... right, for some reason. You feel like you should follow these words.",\ "These words keep echoing in your mind. You find yourself completely fascinated by them.")]") - if(!HAS_TRAIT(owner, "hypnotherapy")) - to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\ + to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\ as long as you act like the words are your highest priority.") - else - to_chat(owner, "You've been hypnotized by this sentence. You feel an incredible desire to follow these words, but are able to resist it somewhat. If it isn't a clear order, you can freely interpret how to do so,\ - however this does not take precedence over your other objectives.") + var/obj/screen/alert/hypnosis/hypno_alert = owner.throw_alert("hypnosis", /obj/screen/alert/hypnosis) + hypno_alert.desc = "\"[hypnotic_phrase]\"... your mind seems to be fixated on this concept." ..() /datum/brain_trauma/hypnosis/on_lose() message_admins("[ADMIN_LOOKUPFLW(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.") log_game("[key_name(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.") - to_chat(owner, "You suddenly snap out of your fixation. The phrase '[hypnotic_phrase]' no longer feels important to you.") + to_chat(owner, "You suddenly snap out of your hypnosis. The phrase '[hypnotic_phrase]' no longer feels important to you.") + owner.clear_alert("hypnosis") ..() /datum/brain_trauma/hypnosis/on_life() @@ -67,6 +67,5 @@ if(2) new /datum/hallucination/chat(owner, TRUE, FALSE, "[hypnotic_phrase]") -/datum/brain_trauma/hypnosis/on_hear(message, speaker, message_language, raw_message, radio_freq) - message = target_phrase.Replace(message, "$1") - return message +/datum/brain_trauma/hypnosis/handle_hearing(datum/source, list/hearing_args) + hearing_args[HEARING_MESSAGE] = target_phrase.Replace(hearing_args[HEARING_MESSAGE], "$1") diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 912dd0dd..47a96e6e 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -8,6 +8,10 @@ var/friend_initialized = FALSE /datum/brain_trauma/special/imaginary_friend/on_gain() + var/mob/living/M = owner + if(M.stat == DEAD || !M.client) + qdel(src) + return ..() make_friend() get_ghost() @@ -43,7 +47,7 @@ /datum/brain_trauma/special/imaginary_friend/proc/get_ghost() set waitfor = FALSE - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend) + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend, POLL_IGNORE_IMAGINARYFRIEND) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) friend.key = C.key @@ -74,26 +78,34 @@ /mob/camera/imaginary_friend/Login() ..() - to_chat(src, "You are the imaginary friend of [owner]!") - to_chat(src, "You are absolutely loyal to your friend, no matter what.") - to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.") + greet() Show() +/mob/camera/imaginary_friend/proc/greet() + to_chat(src, "You are the imaginary friend of [owner]!") + to_chat(src, "You are absolutely loyal to your friend, no matter what.") + to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.") + /mob/camera/imaginary_friend/Initialize(mapload, _trauma) . = ..() - var/gender = pick(MALE, FEMALE) - real_name = random_unique_name(gender) - name = real_name + trauma = _trauma owner = trauma.owner copy_known_languages_from(owner, TRUE) - human_image = get_flat_human_icon(null, pick(SSjob.occupations)) + + setup_friend() join = new join.Grant(src) hide = new hide.Grant(src) +/mob/camera/imaginary_friend/proc/setup_friend() + var/gender = pick(MALE, FEMALE) + real_name = random_unique_name(gender) + name = real_name + human_image = get_flat_human_icon(null, pick(SSjob.occupations)) + /mob/camera/imaginary_friend/proc/Show() if(!client) //nobody home return @@ -132,7 +144,7 @@ if(client.prefs.muted & MUTE_IC) to_chat(src, "You cannot send IC messages (muted).") return - if (src.client.handle_spam_prevention(message,MUTE_IC)) + if (!(ignore_spam || forced) && src.client.handle_spam_prevention(message,MUTE_IC)) return friend_talk(message) @@ -220,4 +232,42 @@ var/mob/camera/imaginary_friend/I = owner I.hidden = !I.hidden I.Show() - update_status() \ No newline at end of file + update_status() + +//down here is the trapped mind +//like imaginary friend but a lot less imagination and more like mind prison// + +/datum/brain_trauma/special/imaginary_friend/trapped_owner + name = "Trapped Victim" + desc = "Patient appears to be targeted by an invisible entity." + gain_text = "" + lose_text = "" + random_gain = FALSE + +/datum/brain_trauma/special/imaginary_friend/trapped_owner/make_friend() + friend = new /mob/camera/imaginary_friend/trapped(get_turf(owner), src) + +/datum/brain_trauma/special/imaginary_friend/trapped_owner/reroll_friend() //no rerolling- it's just the last owner's hell + if(friend.client) //reconnected + return + friend_initialized = FALSE + QDEL_NULL(friend) + qdel(src) + +/datum/brain_trauma/special/imaginary_friend/trapped_owner/get_ghost() //no randoms + return + +/mob/camera/imaginary_friend/trapped + name = "figment of imagination?" + real_name = "figment of imagination?" + desc = "The previous host of this body." + +/mob/camera/imaginary_friend/trapped/greet() + to_chat(src, "You have managed to hold on as a figment of the new host's imagination!") + to_chat(src, "All hope is lost for you, but at least you may interact with your host. You do not have to be loyal to them.") + to_chat(src, "You cannot directly influence the world around you, but you can see what the host cannot.") + +/mob/camera/imaginary_friend/trapped/setup_friend() + real_name = "[owner.real_name]?" + name = real_name + human_image = icon('icons/mob/lavaland/lavaland_monsters.dmi', icon_state = "curseblob") diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm index c049a7db..012f771a 100644 --- a/code/datums/brain_damage/mild.dm +++ b/code/datums/brain_damage/mild.dm @@ -64,21 +64,21 @@ name = "Speech Impediment" desc = "Patient is unable to form coherent sentences." scan_desc = "communication disorder" - gain_text = "" //mutation will handle the text - lose_text = "" + gain_text = "You can't seem to form any coherent thoughts!" + lose_text = "Your mind feels more clear." /datum/brain_trauma/mild/speech_impediment/on_gain() ADD_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT) - . = ..() + ..() /datum/brain_trauma/mild/speech_impediment/on_lose() REMOVE_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT) - . = ..() + ..() /datum/brain_trauma/mild/concussion name = "Concussion" desc = "Patient's brain is concussed." - scan_desc = "a concussion" + scan_desc = "concussion" gain_text = "Your head hurts!" lose_text = "The pressure inside your head starts fading." @@ -157,54 +157,108 @@ gain_text = "Your muscles feel oddly faint." lose_text = "You feel in control of your muscles again." -/datum/brain_trauma/mild/muscle_spasms/on_life() - if(prob(7)) - switch(rand(1,5)) - if(1) - if(owner.canmove && !isspaceturf(owner.loc)) - to_chat(owner, "Your leg spasms!") - step(owner, pick(GLOB.cardinals)) - if(2) - if(owner.incapacitated()) - return - var/obj/item/I = owner.get_active_held_item() - if(I) - to_chat(owner, "Your fingers spasm!") - owner.log_message("used [I] due to a Muscle Spasm", LOG_ATTACK) - I.attack_self(owner) - if(3) - var/prev_intent = owner.a_intent - owner.a_intent = INTENT_HARM - - var/range = 1 - if(istype(owner.get_active_held_item(), /obj/item/gun)) //get targets to shoot at - range = 7 - - var/list/mob/living/targets = list() - for(var/mob/M in oview(owner, range)) - if(isliving(M)) - targets += M - if(LAZYLEN(targets)) - to_chat(owner, "Your arm spasms!") - owner.log_message(" attacked someone due to a Muscle Spasm") //the following attack will log itself - owner.ClickOn(pick(targets)) - owner.a_intent = prev_intent - if(4) - var/prev_intent = owner.a_intent - owner.a_intent = INTENT_HARM - to_chat(owner, "Your arm spasms!") - owner.log_message("attacked [owner.p_them()]self to a Muscle Spasm", LOG_ATTACK) - owner.ClickOn(owner) - owner.a_intent = prev_intent - if(5) - if(owner.incapacitated()) - return - var/obj/item/I = owner.get_active_held_item() - var/list/turf/targets = list() - for(var/turf/T in oview(owner, 3)) - targets += T - if(LAZYLEN(targets) && I) - to_chat(owner, "Your arm spasms!") - owner.log_message("threw [I] due to a Muscle Spasm", LOG_ATTACK) - owner.throw_item(pick(targets)) +/datum/brain_trauma/mild/muscle_spasms/on_gain() + owner.apply_status_effect(STATUS_EFFECT_SPASMS) ..() + +/datum/brain_trauma/mild/muscle_spasms/on_lose() + owner.remove_status_effect(STATUS_EFFECT_SPASMS) + ..() + +/datum/brain_trauma/mild/nervous_cough + name = "Nervous Cough" + desc = "Patient feels a constant need to cough." + scan_desc = "nervous cough" + gain_text = "Your throat itches incessantly..." + lose_text = "Your throat stops itching." + +/datum/brain_trauma/mild/nervous_cough/on_life() + if(prob(12) && !HAS_TRAIT(owner, TRAIT_SOOTHED_THROAT)) + if(prob(5)) + to_chat(owner, "[pick("You have a coughing fit!", "You can't stop coughing!")]") + owner.Stun(20) + owner.emote("cough") + addtimer(CALLBACK(owner, /mob/.proc/emote, "cough"), 6) + addtimer(CALLBACK(owner, /mob/.proc/emote, "cough"), 12) + owner.emote("cough") + ..() + +/datum/brain_trauma/mild/expressive_aphasia + name = "Expressive Aphasia" + desc = "Patient is affected by partial loss of speech leading to a reduced vocabulary." + scan_desc = "inability to form complex sentences" + gain_text = "You lose your grasp on complex words." + lose_text = "You feel your vocabulary returning to normal again." + + var/static/list/common_words = world.file2list("strings/1000_most_common.txt") + +/datum/brain_trauma/mild/expressive_aphasia/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message) + var/list/message_split = splittext(message, " ") + var/list/new_message = list() + + for(var/word in message_split) + var/suffix = copytext(word,-1) + + // Check if we have a suffix and break it out of the word + if(suffix in list("." , "," , ";" , "!" , ":" , "?")) + word = copytext(word,1,-1) + else + suffix = "" + + word = html_decode(word) + + if(lowertext(word) in common_words) + new_message += word + suffix + else + if(prob(30) && message_split.len > 2) + new_message += pick("uh","erm") + break + else + var/list/charlist = string2charlist(word) // Stupid shit code + shuffle_inplace(charlist) + charlist.len = round(charlist.len * 0.5,1) + new_message += html_encode(jointext(charlist,"")) + suffix + + message = jointext(new_message, " ") + + speech_args[SPEECH_MESSAGE] = trim(message) + +/datum/brain_trauma/mild/mind_echo + name = "Mind Echo" + desc = "Patient's language neurons do not terminate properly, causing previous speech patterns to occasionally resurface spontaneously." + scan_desc = "looping neural pattern" + gain_text = "You feel a faint echo of your thoughts..." + lose_text = "The faint echo fades away." + var/list/hear_dejavu = list() + var/list/speak_dejavu = list() + +/datum/brain_trauma/mild/mind_echo/handle_hearing(datum/source, list/hearing_args) + if(owner == hearing_args[HEARING_SPEAKER]) + return + if(hear_dejavu.len >= 5) + if(prob(25)) + var/deja_vu = pick_n_take(hear_dejavu) + var/static/regex/quoted_spoken_message = regex("\".+\"", "gi") + hearing_args[HEARING_MESSAGE] = quoted_spoken_message.Replace(hearing_args[HEARING_MESSAGE], "\"[deja_vu]\"") //Quotes included to avoid cases where someone says part of their name + return + if(hear_dejavu.len >= 15) + if(prob(50)) + popleft(hear_dejavu) //Remove the oldest + hear_dejavu += hearing_args[HEARING_RAW_MESSAGE] + else + hear_dejavu += hearing_args[HEARING_RAW_MESSAGE] + +/datum/brain_trauma/mild/mind_echo/handle_speech(datum/source, list/speech_args) + if(speak_dejavu.len >= 5) + if(prob(25)) + var/deja_vu = pick_n_take(speak_dejavu) + speech_args[SPEECH_MESSAGE] = deja_vu + return + if(speak_dejavu.len >= 15) + if(prob(50)) + popleft(speak_dejavu) //Remove the oldest + speak_dejavu += speech_args[SPEECH_MESSAGE] + else + speak_dejavu += speech_args[SPEECH_MESSAGE] \ No newline at end of file diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm index f802555c..034f2baa 100644 --- a/code/datums/brain_damage/phobia.dm +++ b/code/datums/brain_damage/phobia.dm @@ -2,8 +2,8 @@ name = "Phobia" desc = "Patient is unreasonably afraid of something." scan_desc = "phobia" - gain_text = "" - lose_text = "" + gain_text = "You start finding default values very unnerving..." + lose_text = "You no longer feel afraid of default values." var/phobia_type var/next_check = 0 var/next_scare = 0 @@ -14,8 +14,10 @@ var/list/trigger_turfs var/list/trigger_species -/datum/brain_trauma/mild/phobia/New(specific_type) - phobia_type = specific_type +/datum/brain_trauma/mild/phobia/New(new_phobia_type) + if(new_phobia_type) + phobia_type = new_phobia_type + if(!phobia_type) phobia_type = pick(SStraumas.phobia_types) @@ -29,6 +31,11 @@ trigger_species = SStraumas.phobia_species[phobia_type] ..() + +/datum/brain_trauma/mild/phobia/on_clone() + if(clonable) + return new type(phobia_type) + /datum/brain_trauma/mild/phobia/on_life() ..() if(HAS_TRAIT(owner, TRAIT_FEARLESS)) @@ -44,6 +51,12 @@ if(is_type_in_typecache(O, trigger_objs)) freak_out(O) return + for(var/mob/living/carbon/human/HU in seen_atoms) //check equipment for trigger items + for(var/X in HU.get_all_slots() | HU.held_items) + var/obj/I = X + if(!QDELETED(I) && is_type_in_typecache(I, trigger_objs)) + freak_out(I) + return if(LAZYLEN(trigger_turfs)) for(var/turf/T in seen_atoms) @@ -51,45 +64,41 @@ freak_out(T) return - if(LAZYLEN(trigger_mobs) || LAZYLEN(trigger_objs)) + seen_atoms -= owner //make sure they aren't afraid of themselves. + if(LAZYLEN(trigger_mobs) || LAZYLEN(trigger_species)) for(var/mob/M in seen_atoms) if(is_type_in_typecache(M, trigger_mobs)) freak_out(M) return - else if(ishuman(M)) //check their equipment for trigger items + else if(ishuman(M)) //check their species var/mob/living/carbon/human/H = M if(LAZYLEN(trigger_species) && H.dna && H.dna.species && is_type_in_typecache(H.dna.species, trigger_species)) freak_out(H) + return - for(var/X in H.get_all_slots() | H.held_items) - var/obj/I = X - if(!QDELETED(I) && is_type_in_typecache(I, trigger_objs)) - freak_out(I) - return - -/datum/brain_trauma/mild/phobia/on_hear(message, speaker, message_language, raw_message, radio_freq) +/datum/brain_trauma/mild/phobia/handle_hearing(datum/source, list/hearing_args) if(!owner.can_hear() || world.time < next_scare) //words can't trigger you if you can't hear them *taps head* - return message + return if(HAS_TRAIT(owner, TRAIT_FEARLESS)) - return message + return for(var/word in trigger_words) - var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i") + var/regex/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i") - if(findtext(raw_message, reg)) + if(findtext(hearing_args[HEARING_RAW_MESSAGE], reg)) addtimer(CALLBACK(src, .proc/freak_out, null, word), 10) //to react AFTER the chat message + hearing_args[HEARING_MESSAGE] = reg.Replace(hearing_args[HEARING_MESSAGE], "$1") break - return message /datum/brain_trauma/mild/phobia/handle_speech(datum/source, list/speech_args) if(HAS_TRAIT(owner, TRAIT_FEARLESS)) return for(var/word in trigger_words) - var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i") + var/regex/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i") if(findtext(speech_args[SPEECH_MESSAGE], reg)) - to_chat(owner, "You can't bring yourself to say the word \"[word]\"!") + to_chat(owner, "You can't bring yourself to say the word \"[word]\"!") speech_args[SPEECH_MESSAGE] = "" /datum/brain_trauma/mild/phobia/proc/freak_out(atom/reason, trigger_word) @@ -125,6 +134,76 @@ owner.Jitter(10) owner.stuttering += 10 +// Defined phobia types for badminry, not included in the RNG trauma pool to avoid diluting. + +/datum/brain_trauma/mild/phobia/spiders + phobia_type = "spiders" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/space + phobia_type = "space" + random_gain = FALSE + /datum/brain_trauma/mild/phobia/security phobia_type = "security" random_gain = FALSE + +/datum/brain_trauma/mild/phobia/clowns + phobia_type = "clowns" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/greytide + phobia_type = "greytide" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/lizards + phobia_type = "lizards" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/skeletons + phobia_type = "skeletons" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/snakes + phobia_type = "snakes" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/robots + phobia_type = "robots" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/doctors + phobia_type = "doctors" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/authority + phobia_type = "authority" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/supernatural + phobia_type = "the supernatural" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/aliens + phobia_type = "aliens" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/strangers + phobia_type = "strangers" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/birds + phobia_type = "birds" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/falling + phobia_type = "falling" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/anime + phobia_type = "anime" + random_gain = FALSE + +/datum/brain_trauma/mild/phobia/conspiracies + phobia_type = "conspiracies" + random_gain = FALSE diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index 890e9cf9..0e08c4cd 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -119,7 +119,7 @@ owner.update_disabled_bodyparts() /datum/brain_trauma/severe/paralysis/paraplegic - //can_gain = FALSE maybe breaks. + random_gain = FALSE paralysis_type = "legs" resilience = TRAUMA_RESILIENCE_ABSOLUTE @@ -149,7 +149,7 @@ /datum/brain_trauma/severe/monophobia name = "Monophobia" desc = "Patient feels sick and distressed when not around other people, leading to potentially lethal levels of stress." - scan_desc = "severe monophobia" + scan_desc = "monophobia" gain_text = "" lose_text = "You feel like you could be safe on your own." var/stress = 0 @@ -168,7 +168,7 @@ if(stress > 10 && (prob(5))) stress_reaction() else - stress -= 4 + stress = max(stress - 4, 0) /datum/brain_trauma/severe/monophobia/proc/check_alone() if(HAS_TRAIT(owner, TRAIT_BLIND)) diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index d9e6f006..b52c7d39 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -22,6 +22,14 @@ else speak("neutral", prob(25)) +/datum/brain_trauma/special/godwoken/on_gain() + ADD_TRAIT(owner, TRAIT_HOLY, TRAUMA_TRAIT) + ..() + +/datum/brain_trauma/special/godwoken/on_lose() + REMOVE_TRAIT(owner, TRAIT_HOLY, TRAUMA_TRAIT) + ..() + /datum/brain_trauma/special/godwoken/proc/speak(type, include_owner = FALSE) var/message switch(type) @@ -36,7 +44,7 @@ else message = pick_list_replacements(BRAIN_DAMAGE_FILE, "god_neutral") - playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 200, 1, 5) + playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 200, TRUE, 5) voice_of_god(message, owner, list("colossus","yell"), 2.5, include_owner, FALSE) /datum/brain_trauma/special/bluespace_prophet @@ -134,7 +142,101 @@ /datum/brain_trauma/special/psychotic_brawling/bath_salts name = "Chemical Violent Psychosis" - random_gain = FALSE + clonable = FALSE + +/datum/brain_trauma/special/tenacity + name = "Tenacity" + desc = "Patient is psychologically unaffected by pain and injuries, and can remain standing far longer than a normal person." + scan_desc = "traumatic neuropathy" + gain_text = "You suddenly stop feeling pain." + lose_text = "You realize you can feel pain again." + +/datum/brain_trauma/special/tenacity/on_gain() + ADD_TRAIT(owner, TRAIT_NOSOFTCRIT, TRAUMA_TRAIT) + ADD_TRAIT(owner, TRAIT_NOHARDCRIT, TRAUMA_TRAIT) + ..() + +/datum/brain_trauma/special/tenacity/on_lose() + REMOVE_TRAIT(owner, TRAIT_NOSOFTCRIT, TRAUMA_TRAIT) + REMOVE_TRAIT(owner, TRAIT_NOHARDCRIT, TRAUMA_TRAIT) + ..() + +/datum/brain_trauma/special/death_whispers + name = "Functional Cerebral Necrosis" + desc = "Patient's brain is stuck in a functional near-death state, causing occasional moments of lucid hallucinations, which are often interpreted as the voices of the dead." + scan_desc = "chronic functional necrosis" + gain_text = "You feel dead inside." + lose_text = "You feel alive again." + var/active = FALSE + +/datum/brain_trauma/special/death_whispers/on_life() + ..() + if(!active && prob(2)) + whispering() + +/datum/brain_trauma/special/death_whispers/on_lose() + if(active) + cease_whispering() + ..() + +/datum/brain_trauma/special/death_whispers/proc/whispering() + ADD_TRAIT(owner, TRAIT_SIXTHSENSE, TRAUMA_TRAIT) + active = TRUE + addtimer(CALLBACK(src, .proc/cease_whispering), rand(50, 300)) + +/datum/brain_trauma/special/death_whispers/proc/cease_whispering() + REMOVE_TRAIT(owner, TRAIT_SIXTHSENSE, TRAUMA_TRAIT) + active = FALSE + +/datum/brain_trauma/special/existential_crisis + name = "Existential Crisis" + desc = "Patient's hold on reality becomes faint, causing occasional bouts of non-existence." + scan_desc = "existential crisis" + gain_text = "You feel less real." + lose_text = "You feel more substantial again." + var/obj/effect/abstract/sync_holder/veil/veil + var/next_crisis = 0 + +/datum/brain_trauma/special/existential_crisis/on_life() + ..() + if(!veil && world.time > next_crisis && prob(3)) + if(isturf(owner.loc)) + fade_out() + +/datum/brain_trauma/special/existential_crisis/on_lose() + if(veil) + fade_in() + ..() + +/datum/brain_trauma/special/existential_crisis/proc/fade_out() + if(veil) + return + var/duration = rand(50, 450) + veil = new(owner.drop_location()) + to_chat(owner, "[pick("You stop thinking for a moment. Therefore you are not.",\ + "To be or not to be...",\ + "Why exist?",\ + "You stop keeping it real.",\ + "Your grip on existence slips.",\ + "Do you even exist?",\ + "You simply fade away.")]") + owner.forceMove(veil) + SEND_SIGNAL(owner, COMSIG_MOVABLE_SECLUDED_LOCATION) + for(var/thing in owner) + var/atom/movable/AM = thing + SEND_SIGNAL(AM, COMSIG_MOVABLE_SECLUDED_LOCATION) + next_crisis = world.time + 600 + addtimer(CALLBACK(src, .proc/fade_in), duration) + +/datum/brain_trauma/special/existential_crisis/proc/fade_in() + QDEL_NULL(veil) + to_chat(owner, "You fade back into reality.") + next_crisis = world.time + 600 + +//base sync holder is in desynchronizer.dm +/obj/effect/abstract/sync_holder/veil + name = "non-existence" + desc = "Existence is just a state of mind." /datum/brain_trauma/special/beepsky name = "Criminal" @@ -142,6 +244,7 @@ scan_desc = "criminal mind" gain_text = "Justice is coming for you." lose_text = "You were absolved for your crimes." + clonable = FALSE random_gain = FALSE var/obj/effect/hallucination/simple/securitron/beepsky @@ -201,4 +304,4 @@ /obj/effect/hallucination/simple/securitron/Destroy() STOP_PROCESSING(SSfastprocess,src) - return ..() \ No newline at end of file + return ..() diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm index 653b8a98..87665961 100644 --- a/code/datums/brain_damage/split_personality.dm +++ b/code/datums/brain_damage/split_personality.dm @@ -13,6 +13,10 @@ var/mob/living/split_personality/owner_backseat /datum/brain_trauma/severe/split_personality/on_gain() + var/mob/living/M = owner + if(M.stat == DEAD) //No use assigning people to a corpse + qdel(src) + return ..() make_backseats() get_ghost() @@ -23,7 +27,7 @@ /datum/brain_trauma/severe/split_personality/proc/get_ghost() set waitfor = FALSE - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat) + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat, POLL_IGNORE_SPLITPERSONALITY) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) stranger_backseat.key = C.key @@ -191,13 +195,13 @@ /datum/brain_trauma/severe/split_personality/brainwashing/on_life() return //no random switching -/datum/brain_trauma/severe/split_personality/brainwashing/on_hear(message, speaker, message_language, raw_message, radio_freq) - if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == speaker) - return message +/datum/brain_trauma/severe/split_personality/brainwashing/handle_hearing(datum/source, list/hearing_args) + if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == hearing_args[HEARING_SPEAKER]) + return + var/message = hearing_args[HEARING_MESSAGE] if(findtext(message, codeword)) - message = replacetext(message, codeword, "[codeword]") + hearing_args[HEARING_MESSAGE] = replacetext(message, codeword, "[codeword]") addtimer(CALLBACK(src, /datum/brain_trauma/severe/split_personality.proc/switch_personalities), 10) - return message /datum/brain_trauma/severe/split_personality/brainwashing/handle_speech(datum/source, list/speech_args) if(findtext(speech_args[SPEECH_MESSAGE], codeword)) diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm index ef52a8fc..11a260d2 100644 --- a/code/datums/components/nanites.dm +++ b/code/datums/components/nanites.dm @@ -53,7 +53,6 @@ RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access) RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) - RegisterSignal(parent, COMSIG_MOVABLE_HEAR, .proc/on_hear) RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) @@ -191,11 +190,6 @@ var/datum/nanite_program/NP = X NP.on_death(gibbed) -/datum/component/nanites/proc/on_hear(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - /datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source") for(var/X in programs) var/datum/nanite_program/NP = X diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm index bbcb0d88..91928656 100644 --- a/code/datums/components/stationloving.dm +++ b/code/datums/components/stationloving.dm @@ -8,6 +8,7 @@ if(!ismovableatom(parent)) return COMPONENT_INCOMPATIBLE RegisterSignal(parent, list(COMSIG_MOVABLE_Z_CHANGED), .proc/check_in_bounds) + RegisterSignal(parent, list(COMSIG_MOVABLE_SECLUDED_LOCATION), .proc/relocate) RegisterSignal(parent, list(COMSIG_PARENT_PREQDELETED), .proc/check_deletion) RegisterSignal(parent, list(COMSIG_ITEM_IMBUE_SOUL), .proc/check_soul_imbue) src.inform_admins = inform_admins @@ -32,6 +33,7 @@ var/atom/movable/AM = parent AM.forceMove(targetturf) + to_chat(get(parent, /mob), "You can't help but feel that you just lost something back there...") // move the disc, so ghosts remain orbiting it even if it's "destroyed" return targetturf @@ -40,7 +42,6 @@ return else var/turf/currentturf = get_turf(src) - to_chat(get(parent, /mob), "You can't help but feel that you just lost something back there...") var/turf/targetturf = relocate() log_game("[parent] has been moved out of bounds in [loc_name(currentturf)]. Moving it to [loc_name(targetturf)].") if(inform_admins) diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index 20bba021..a9ba87b1 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -635,8 +635,7 @@ datum/status_effect/pacify tick_interval = 10 examine_text = "SUBJECTPRONOUN seems slow and unfocused." var/stun = TRUE - var/triggered = FALSE - alert_type = null + alert_type = /obj/screen/alert/status_effect/trance /obj/screen/alert/status_effect/trance name = "Trance" @@ -644,17 +643,6 @@ datum/status_effect/pacify icon_state = "high" /datum/status_effect/trance/tick() - if(HAS_TRAIT(owner, "hypnotherapy")) - if(triggered == TRUE) - UnregisterSignal(owner, COMSIG_MOVABLE_HEAR) - RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize) - ADD_TRAIT(owner, TRAIT_MUTE, "trance") - if(!owner.has_quirk(/datum/quirk/monochromatic)) - owner.add_client_colour(/datum/client_colour/monochrome) - to_chat(owner, "[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]") - triggered = FALSE - else - return if(stun) owner.Stun(60, TRUE, TRUE) owner.dizziness = 20 @@ -662,48 +650,33 @@ datum/status_effect/pacify /datum/status_effect/trance/on_apply() if(!iscarbon(owner)) return FALSE - if(HAS_TRAIT(owner, "hypnotherapy")) - RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/listen) - return TRUE - alert_type = /obj/screen/alert/status_effect/trance RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize) ADD_TRAIT(owner, TRAIT_MUTE, "trance") - if(!owner.has_quirk(/datum/quirk/monochromatic)) - owner.add_client_colour(/datum/client_colour/monochrome) + owner.add_client_colour(/datum/client_colour/monochrome/trance) owner.visible_message("[stun ? "[owner] stands still as [owner.p_their()] eyes seem to focus on a distant point." : ""]", \ "[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]") return TRUE -/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE, source_quirk = FALSE)//hypnoquirk makes no visible message, prevents self antag messages, and places phrase below objectives. +/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE) duration = _duration stun = _stun - if(source_quirk == FALSE && HAS_TRAIT(owner, "hypnotherapy")) - REMOVE_TRAIT(owner, "hypnotherapy", ROUNDSTART_TRAIT) return ..() /datum/status_effect/trance/on_remove() UnregisterSignal(owner, COMSIG_MOVABLE_HEAR) REMOVE_TRAIT(owner, TRAIT_MUTE, "trance") owner.dizziness = 0 - if(!owner.has_quirk(/datum/quirk/monochromatic)) - owner.remove_client_colour(/datum/client_colour/monochrome) + owner.remove_client_colour(/datum/client_colour/monochrome/trance) to_chat(owner, "You snap out of your trance!") -/datum/status_effect/trance/proc/listen(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - to_chat(owner, "[speaker] accidentally sets off your implanted trigger, sending you into a hypnotic daze!") - triggered = TRUE - -/datum/status_effect/trance/proc/hypnotize(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) +/datum/status_effect/trance/proc/hypnotize(datum/source, list/hearing_args) if(!owner.can_hear()) return - if(speaker == owner) + if(hearing_args[HEARING_SPEAKER] == owner) return var/mob/living/carbon/C = owner C.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY) //clear previous hypnosis - if(HAS_TRAIT(C, "hypnotherapy")) - addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message, TRUE), 10) - else - addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message), 10) + addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, hearing_args[HEARING_RAW_MESSAGE]), 10) addtimer(CALLBACK(C, /mob/living.proc/Stun, 60, TRUE, TRUE), 15) //Take some time to think about it qdel(src) @@ -729,3 +702,59 @@ datum/status_effect/pacify name = "Genetic Breakdown" desc = "I don't feel so good. Your body can't handle the mutations! You have one minute to remove your mutations, or you will be met with a horrible fate." icon_state = "dna_melt" + +/datum/status_effect/spasms + id = "spasms" + status_type = STATUS_EFFECT_MULTIPLE + alert_type = null + +/datum/status_effect/spasms/tick() + if(prob(15)) + switch(rand(1,5)) + if(1) + if((!owner.lying && !owner.buckled) && isturf(owner.loc)) + to_chat(owner, "Your leg spasms!") + step(owner, pick(GLOB.cardinals)) + if(2) + if(owner.incapacitated()) + return + var/obj/item/I = owner.get_active_held_item() + if(I) + to_chat(owner, "Your fingers spasm!") + owner.log_message("used [I] due to a Muscle Spasm", LOG_ATTACK) + I.attack_self(owner) + if(3) + var/prev_intent = owner.a_intent + owner.a_intent = INTENT_HARM + + var/range = 1 + if(istype(owner.get_active_held_item(), /obj/item/gun)) //get targets to shoot at + range = 7 + + var/list/mob/living/targets = list() + for(var/mob/M in oview(owner, range)) + if(isliving(M)) + targets += M + if(LAZYLEN(targets)) + to_chat(owner, "Your arm spasms!") + owner.log_message(" attacked someone due to a Muscle Spasm", LOG_ATTACK) //the following attack will log itself + owner.ClickOn(pick(targets)) + owner.a_intent = prev_intent + if(4) + var/prev_intent = owner.a_intent + owner.a_intent = INTENT_HARM + to_chat(owner, "Your arm spasms!") + owner.log_message("attacked [owner.p_them()]self to a Muscle Spasm", LOG_ATTACK) + owner.ClickOn(owner) + owner.a_intent = prev_intent + if(5) + if(owner.incapacitated()) + return + var/obj/item/I = owner.get_active_held_item() + var/list/turf/targets = list() + for(var/turf/T in oview(owner, 3)) + targets += T + if(LAZYLEN(targets) && I) + to_chat(owner, "Your arm spasms!") + owner.log_message("threw [I] due to a Muscle Spasm", LOG_ATTACK) + owner.throw_item(pick(targets)) \ No newline at end of file diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 655863e0..cb2b4174 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -69,3 +69,17 @@ /datum/status_effect/in_love/tick() if(date) new /obj/effect/temp_visual/love_heart/invisible(get_turf(date.loc), owner) + +/datum/status_effect/throat_soothed + id = "throat_soothed" + duration = 60 SECONDS + status_type = STATUS_EFFECT_REFRESH + alert_type = null + +/datum/status_effect/throat_soothed/on_apply() + . = ..() + ADD_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]") + +/datum/status_effect/throat_soothed/on_remove() + . = ..() + REMOVE_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]") \ No newline at end of file diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index bc1cfba1..0d84aab7 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -64,6 +64,12 @@ owner = null qdel(src) +/datum/status_effect/proc/refresh() + var/original_duration = initial(duration) + if(original_duration == -1) + return + duration = world.time + original_duration + //clickdelay/nextmove modifiers! /datum/status_effect/proc/nextmove_modifier() return 1 @@ -92,6 +98,9 @@ if(S.id == initial(S1.id) && S.status_type) if(S.status_type == STATUS_EFFECT_REPLACE) S.be_replaced() + else if(S.status_type == STATUS_EFFECT_REFRESH) + S.refresh() + return else return var/list/arguments = args.Copy() diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 6dbf96e3..00614f91 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1,870 +1,870 @@ -/atom - layer = TURF_LAYER - plane = GAME_PLANE - var/level = 2 - var/article // If non-null, overrides a/an/some in all cases - - var/flags_1 = NONE - var/interaction_flags_atom = NONE - var/datum/reagents/reagents = null - - //This atom's HUD (med/sec, etc) images. Associative list. - var/list/image/hud_list = null - //HUD images that this atom can provide. - var/list/hud_possible - - /// Last name used to calculate a color for the chatmessage overlays - var/chat_color_name - /// Last color calculated for the the chatmessage overlays - var/chat_color - /// A luminescence-shifted value of the last color calculated for chatmessage overlays - var/chat_color_darkened - - //Value used to increment ex_act() if reactionary_explosions is on - var/explosion_block = 0 - - 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... - - 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/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays - var/list/managed_overlays - var/datum/proximity_monitor/proximity_monitor - var/buckle_message_cooldown = 0 - var/fingerprintslast - - var/list/filter_data //For handling persistent filters - - var/datum/component/orbiter/orbiters - - var/rad_flags = NONE // Will move to flags_1 when i can be arsed to - var/rad_insulation = RAD_NO_INSULATION - - var/icon/blood_splatter_icon - var/icon/cum_splatter_icon - var/list/fingerprints - var/list/fingerprintshidden - var/list/blood_DNA - var/list/suit_fibers - -/atom/New(loc, ...) - //atom creation method that preloads variables at creation - if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() - GLOB._preloader.load(src) - - if(datum_flags & DF_USE_TAG) - GenerateTag() - - 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(flags_1 & INITIALIZED_1) - stack_trace("Warning: [src]([type]) initialized multiple times!") - flags_1 |= INITIALIZED_1 - - //atom color stuff - 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 guaranteed to be on afterwards anyways. - - if (canSmoothWith) - canSmoothWith = typelist("canSmoothWith", canSmoothWith) - - 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/Destroy() - if(alternate_appearances) - for(var/K in alternate_appearances) - var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K] - AA.remove_from_hud(src) - - if(reagents) - qdel(reagents) - - LAZYCLEARLIST(overlays) - LAZYCLEARLIST(priority_overlays) - - QDEL_NULL(light) - - return ..() - -/atom/proc/handle_ricochet(obj/item/projectile/P) - return - -/atom/proc/CanPass(atom/movable/mover, turf/target) - return !density - -/atom/proc/onCentCom() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(is_reserved_level(T.z)) - for(var/A in SSshuttle.mobile) - var/obj/docking_port/mobile/M = A - if(M.launch_status == ENDGAME_TRANSIT) - for(var/place in M.shuttle_areas) - var/area/shuttle/shuttle_area = place - if(T in shuttle_area) - return TRUE - - if(!is_centcom_level(T.z))//if not, don't bother - return FALSE - - //Check for centcom itself - if(istype(T.loc, /area/centcom)) - return TRUE - - //Check for centcom shuttles - for(var/A in SSshuttle.mobile) - var/obj/docking_port/mobile/M = A - if(M.launch_status == ENDGAME_LAUNCHED) - for(var/place in M.shuttle_areas) - var/area/shuttle/shuttle_area = place - if(T in shuttle_area) - return TRUE - -/atom/proc/onSyndieBase() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(!is_centcom_level(T.z))//if not, don't bother - return FALSE - - if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod)) - return TRUE - - return FALSE - -/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) - log_combat(user, src, "punched", "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(ismovableatom(A)) - var/atom/movable/M = A - if(isliving(M.loc)) - var/mob/living/L = M.loc - L.transferItemToLoc(M, src) - else - M.forceMove(src) - -//common name -/atom/proc/update_multiz(prune_on_fail = FALSE) - return FALSE - -/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(mob/user) - return - -/atom/proc/Bumped(atom/movable/AM) - set waitfor = FALSE - -// Convenience procs to see if a container is open for chemistry handling -/atom/proc/is_open_container() - return is_refillable() && is_drainable() - -/atom/proc/is_injectable(allowmobs = TRUE) - return reagents && (reagents.reagents_holder_flags & (INJECTABLE | REFILLABLE)) - -/atom/proc/is_drawable(allowmobs = TRUE) - return reagents && (reagents.reagents_holder_flags & (DRAWABLE | DRAINABLE)) - -/atom/proc/is_refillable() - return reagents && (reagents.reagents_holder_flags & REFILLABLE) - -/atom/proc/is_drainable() - return reagents && (reagents.reagents_holder_flags & DRAINABLE) - - -/atom/proc/AllowDrop() - return FALSE - -/atom/proc/CheckExit() - return TRUE - -/atom/proc/HasProximity(atom/movable/AM as mob|obj) - return - -/atom/proc/emp_act(severity) - var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) - if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) - wires.emp_pulse() - return protection // Pass the protection value collected here upwards - -/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) - -//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it. -// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first. -/atom/proc/shove_act(mob/living/target, mob/living/user) - return FALSE - -/atom/proc/in_contents_of(container)//can take class or object instance as argument - if(ispath(container)) - if(istype(src.loc, container)) - return TRUE - else if(src in container) - return TRUE - return FALSE - -/atom/proc/update_icon_state() - -/atom/proc/update_overlaysb() - SHOULD_CALL_PARENT(1) - . = list() - SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .) - -/atom/proc/update_iconb() - var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) - - if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) - update_icon_state() - - if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS)) - var/list/new_overlays = update_overlaysb() - if(managed_overlays) - cut_overlay(managed_overlays) - managed_overlays = null - if(length(new_overlays)) - managed_overlays = new_overlays - add_overlay(new_overlays) - -/atom/proc/get_examine_name(mob/user) - . = "\a [src]" - var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]") - if(article) - . = "[article] [src]" - override[EXAMINE_POSITION_ARTICLE] = article - - var/should_override = FALSE - - if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) - should_override = TRUE - - - if(blood_DNA && !istype(src, /obj/effect/decal)) - override[EXAMINE_POSITION_BEFORE] = " blood-stained " - should_override = TRUE - - if(should_override) - . = override.Join("") - -///Generate the full examine string of this atom (including icon for goonchat) -/atom/proc/get_examine_string(mob/user, thats = FALSE) - return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]" - -/atom/proc/examine(mob/user) - . = list("[get_examine_string(user, TRUE)].") - - if(desc) - . += desc - - if(reagents) - if(reagents.reagents_holder_flags & TRANSPARENT) - . += "It contains:" - if(length(reagents.reagent_list)) - if(user.can_see_reagents()) //Show each individual reagent - for(var/datum/reagent/R in reagents.reagent_list) - . += "[R.volume] units of [R.name]" - else //Otherwise, just show the total volume - var/total_volume = 0 - for(var/datum/reagent/R in reagents.reagent_list) - total_volume += R.volume - . += "[total_volume] units of various reagents" - else - . += "Nothing." - else if(reagents.reagents_holder_flags & 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(mob/user) - if(buckle_message_cooldown <= world.time) - buckle_message_cooldown = world.time + 50 - to_chat(user, "You can't move while buckled to [src]!") - return - -/atom/proc/prevent_content_explosion() - return FALSE - -/atom/proc/contents_explosion(severity, target) - return //For handling the effects of explosions on contents that would not normally be effected - -/atom/proc/ex_act(severity, target) - set waitfor = FALSE - contents_explosion(severity, target) - SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) - -/atom/proc/blob_act(obj/structure/blob/B) - SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) - return - -/atom/proc/fire_act(exposed_temperature, exposed_volume) - SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) - return - -/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/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube) - return - -//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() - var/blood_id = get_blood_id() - if(!(blood_id in GLOB.blood_reagent_types)) - return - return list("ANIMAL DNA" = "Y-") - -/mob/living/carbon/get_blood_dna_list() - var/blood_id = get_blood_id() - if(!(blood_id in GLOB.blood_reagent_types)) - 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) - // Returns 0 if we have that blood already - var/new_blood_dna = L.get_blood_dna_list() - if(!new_blood_dna) - return FALSE - LAZYINITLIST(blood_DNA) //if our list of DNA doesn't exist yet, initialise it. - var/old_length = blood_DNA.len - blood_DNA |= new_blood_dna - if(blood_DNA.len == old_length) - return FALSE - return TRUE - -//to add blood dna info to the object's blood_DNA list -/atom/proc/transfer_blood_dna(list/blood_dna, list/datum/disease/diseases) - LAZYINITLIST(blood_DNA) - var/old_length = blood_DNA.len - blood_DNA |= blood_dna - if(blood_DNA.len > old_length) - return TRUE - //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 FALSE - return add_blood_DNA(blood_dna, M.diseases) - -//to add blood onto something, with blood dna info to include. -/atom/proc/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - return FALSE - -/obj/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - return transfer_blood_dna(blood_dna, diseases) - -/obj/item/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - . = ..() - if(!.) - return - add_blood_overlay() - -/obj/item/proc/add_blood_overlay() - if(!blood_DNA.len) - return - if(initial(icon) && initial(icon_state)) - 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.Blend(blood_DNA_to_color(), ICON_MULTIPLY) - add_overlay(blood_splatter_icon) - -/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - . = ..() - transfer_blood = rand(2, 4) - -/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src - if(!B) - B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) - B.transfer_blood_dna(blood_dna, diseases) //give blood info to the blood decal. - return TRUE //we bloodied the floor - -/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - if(head) - head.add_blood_DNA(blood_dna, diseases) - update_inv_head() - else if(wear_mask) - wear_mask.add_blood_DNA(blood_dna, diseases) - update_inv_wear_mask() - if(wear_neck) - wear_neck.add_blood_DNA(blood_dna, diseases) - update_inv_neck() - if(wear_suit) - wear_suit.add_blood_DNA(blood_dna, diseases) - update_inv_wear_suit() - else if(w_uniform) - w_uniform.add_blood_DNA(blood_dna, diseases) - update_inv_w_uniform() - if(gloves) - var/obj/item/clothing/gloves/G = gloves - G.add_blood_DNA(blood_dna, diseases) - else - transfer_blood_dna(blood_dna, diseases) - bloody_hands = rand(2, 4) - update_inv_gloves() //handles bloody hands overlays and updating - return TRUE - -/atom/proc/blood_DNA_to_color() - var/list/colors = list()//first we make a list of all bloodtypes present - for(var/bloop in blood_DNA) - if(colors[blood_DNA[bloop]]) - colors[blood_DNA[bloop]]++ - else - colors[blood_DNA[bloop]] = 1 - - var/final_rgb = BLOOD_COLOR_HUMAN //a default so we don't have white blood graphics if something messed up - - if(colors.len) - var/sum = 0 //this is all shitcode, but it works; trust me - final_rgb = bloodtype_to_color(colors[1]) - sum = colors[colors[1]] - if(colors.len > 1) - var/i = 2 - while(i <= colors.len) - var/tmp = colors[colors[i]] - final_rgb = BlendRGB(final_rgb, bloodtype_to_color(colors[i]), tmp/(tmp+sum)) - sum += tmp - i++ - - return final_rgb - -/atom/proc/clean_blood() - . = blood_DNA? TRUE : FALSE - blood_DNA = null - -/atom/proc/wash_cream() - return TRUE - -/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) - -/atom/proc/acid_act(acidpwr, acid_volume) - SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) - -/atom/proc/emag_act() - SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) - -/atom/proc/rad_act(strength) - SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) - -/atom/proc/narsie_act() - SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) - -/atom/proc/ratvar_act() - SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT) - -/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - return FALSE - -/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode) - return FALSE - -/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user) - if(GetComponent(/datum/component/storage)) - return component_storage_contents_dump_act(src_object, user) - return FALSE - -/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user) - var/list/things = src_object.contents() - var/datum/progressbar/progress = new(user, things.len, src) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress))) - stoplag(1) - qdel(progress) - to_chat(user, "You dump as much of [src_object.parent]'s contents into [STR.insert_preposition]to [src] as you can.") - STR.orient2hud(user) - src_object.orient2hud(user) - if(user.active_storage) //refresh the HUD to show the transfered contents - user.active_storage.close(user) - user.active_storage.show_to(user) - return TRUE - -/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) - return null - -//This proc is called on the location of an atom when the atom is Destroy()'d -/atom/proc/handle_atom_del(atom/A) - SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A) - -//called when the turf the atom resides on is ChangeTurfed -/atom/proc/HandleTurfChange(turf/T) - for(var/a in src) - var/atom/A = a - A.HandleTurfChange(T) - -//the vision impairment to give to the mob whose perspective is set to that atom (e.g. an unfocused camera giving you an impaired vision when looking through it) -/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) - 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 - -/atom/proc/mech_melee_attack(obj/mecha/M) - return - -//If a mob logouts/logins in side of an object you can use this proc -/atom/proc/on_log(login) - if(loc) - loc.on_log(login) - - -/* - Atom Colour Priority System - A System that gives finer control over which atom colour to colour the atom with. - The "highest priority" one is always displayed as opposed to the default of - "whichever was set last is displayed" -*/ - - -/* - 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 - -/atom/vv_edit_var(var_name, var_value) - if(!GLOB.Debug2) - flags_1 |= ADMIN_SPAWNED_1 - . = ..() - switch(var_name) - if("color") - add_atom_colour(color, ADMIN_COLOUR_PRIORITY) - -/atom/vv_get_dropdown() - . = ..() - . += "---" - var/turf/curturf = get_turf(src) - if (curturf) - .["Jump to"] = "?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]" - .["Modify Transform"] = "?_src_=vars;[HrefToken()];modtransform=[REF(src)]" - .["Add reagent"] = "?_src_=vars;[HrefToken()];addreagent=[REF(src)]" - .["Trigger EM pulse"] = "?_src_=vars;[HrefToken()];emp=[REF(src)]" - .["Trigger explosion"] = "?_src_=vars;[HrefToken()];explode=[REF(src)]" - -/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) - -/atom/proc/return_temperature() - return - -// Tool behavior procedure. Redirects to tool-specific procs by default. -// You can override it to catch all tool interactions, for use in complex deconstruction procs. -// Just don't forget to return ..() in the end. -/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) - if(TOOL_ANALYZER) - return analyzer_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 - -/atom/proc/screwdriver_act(mob/living/user, obj/item/I) - SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) - -/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/analyzer_act(mob/living/user, obj/item/I) - return - -/atom/proc/GenerateTag() - return - -// Generic logging helper -/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) - if(!log_globally) - return - - var/log_text = "[key_name(src)] [message] [loc_name(src)]" - switch(message_type) - if(LOG_ATTACK) - log_attack(log_text) - if(LOG_SAY) - log_say(log_text) - if(LOG_WHISPER) - log_whisper(log_text) - if(LOG_EMOTE) - log_emote(log_text) - if(LOG_DSAY) - log_dsay(log_text) - if(LOG_PDA) - log_pda(log_text) - if(LOG_CHAT) - log_chat(log_text) - if(LOG_COMMENT) - log_comment(log_text) - if(LOG_TELECOMMS) - log_telecomms(log_text) - if(LOG_OOC) - log_ooc(log_text) - if(LOG_ADMIN) - log_admin(log_text) - if(LOG_ADMIN_PRIVATE) - log_admin_private(log_text) - if(LOG_ASAY) - log_adminsay(log_text) - if(LOG_OWNERSHIP) - log_game(log_text) - if(LOG_GAME) - log_game(log_text) - else - stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).") - log_game(log_text) - -// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements) -/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null) - var/prefix = tag ? "([tag]) " : "" - var/suffix = forced_by ? " FORCED by [forced_by]" : "" - log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally) - -// Helper for logging of messages with only one sender and receiver -/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag) - if(!tag) - stack_trace("Unspecified tag for private message") - tag = "UNKNOWN" - - source.log_talk(message, message_type, tag="[tag] to [key_name(target)]") - if(source != target) - target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE) - -/* -Proc for attack log creation, because really why not -1 argument is the actor performing the action -2 argument is the target of the action -3 is a verb describing the action (e.g. punched, throwed, kicked, etc.) -4 is a tool with which the action was made (usually an item) -5 is any additional text, which will be appended to the rest of the log line -*/ - -/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null) - var/ssource = key_name(user) - var/starget = key_name(target) - - var/mob/living/living_target = target - var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : "" - - var/sobject = "" - if(object) - sobject = " with [key_name(object)]" - var/saddition = "" - if(addition) - saddition = " [addition]" - - var/postfix = "[sobject][saddition][hp]" - - var/message = "has [what_done] [starget][postfix]" - user.log_message(message, LOG_ATTACK, color="red") - - if(user != target) - var/reverse_message = "has been [what_done] by [ssource][postfix]" - target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE) - -// Filter stuff -/atom/movable/proc/add_filter(name,priority,list/params) - if(!filter_data) - filter_data = list() - var/list/p = params.Copy() - p["priority"] = priority - filter_data[name] = p - update_filters() - -/atom/movable/proc/update_filters() - filters = null - sortTim(filter_data,associative = TRUE) - for(var/f in filter_data) - var/list/data = filter_data[f] - var/list/arguments = data.Copy() - arguments -= "priority" - filters += filter(arglist(arguments)) - -/atom/movable/proc/get_filter(name) - if(filter_data && filter_data[name]) - return filters[filter_data.Find(name)] - -/atom/movable/proc/remove_filter(name) - if(filter_data[name]) - filter_data -= name - update_filters() - return TRUE - -/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) - . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels) +/atom + layer = TURF_LAYER + plane = GAME_PLANE + var/level = 2 + var/article // If non-null, overrides a/an/some in all cases + + var/flags_1 = NONE + var/interaction_flags_atom = NONE + var/datum/reagents/reagents = null + + //This atom's HUD (med/sec, etc) images. Associative list. + var/list/image/hud_list = null + //HUD images that this atom can provide. + var/list/hud_possible + + /// Last name used to calculate a color for the chatmessage overlays + var/chat_color_name + /// Last color calculated for the the chatmessage overlays + var/chat_color + /// A luminescence-shifted value of the last color calculated for chatmessage overlays + var/chat_color_darkened + + //Value used to increment ex_act() if reactionary_explosions is on + var/explosion_block = 0 + + 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... + + 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/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays + var/list/managed_overlays + var/datum/proximity_monitor/proximity_monitor + var/buckle_message_cooldown = 0 + var/fingerprintslast + + var/list/filter_data //For handling persistent filters + + var/datum/component/orbiter/orbiters + + var/rad_flags = NONE // Will move to flags_1 when i can be arsed to + var/rad_insulation = RAD_NO_INSULATION + + var/icon/blood_splatter_icon + var/icon/cum_splatter_icon + var/list/fingerprints + var/list/fingerprintshidden + var/list/blood_DNA + var/list/suit_fibers + +/atom/New(loc, ...) + //atom creation method that preloads variables at creation + if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() + GLOB._preloader.load(src) + + if(datum_flags & DF_USE_TAG) + GenerateTag() + + 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(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + + //atom color stuff + 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 guaranteed to be on afterwards anyways. + + if (canSmoothWith) + canSmoothWith = typelist("canSmoothWith", canSmoothWith) + + 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/Destroy() + if(alternate_appearances) + for(var/K in alternate_appearances) + var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K] + AA.remove_from_hud(src) + + if(reagents) + qdel(reagents) + + LAZYCLEARLIST(overlays) + LAZYCLEARLIST(priority_overlays) + + QDEL_NULL(light) + + return ..() + +/atom/proc/handle_ricochet(obj/item/projectile/P) + return + +/atom/proc/CanPass(atom/movable/mover, turf/target) + return !density + +/atom/proc/onCentCom() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(is_reserved_level(T.z)) + for(var/A in SSshuttle.mobile) + var/obj/docking_port/mobile/M = A + if(M.launch_status == ENDGAME_TRANSIT) + for(var/place in M.shuttle_areas) + var/area/shuttle/shuttle_area = place + if(T in shuttle_area) + return TRUE + + if(!is_centcom_level(T.z))//if not, don't bother + return FALSE + + //Check for centcom itself + if(istype(T.loc, /area/centcom)) + return TRUE + + //Check for centcom shuttles + for(var/A in SSshuttle.mobile) + var/obj/docking_port/mobile/M = A + if(M.launch_status == ENDGAME_LAUNCHED) + for(var/place in M.shuttle_areas) + var/area/shuttle/shuttle_area = place + if(T in shuttle_area) + return TRUE + +/atom/proc/onSyndieBase() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(!is_centcom_level(T.z))//if not, don't bother + return FALSE + + if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod)) + return TRUE + + return FALSE + +/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) + log_combat(user, src, "punched", "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(ismovableatom(A)) + var/atom/movable/M = A + if(isliving(M.loc)) + var/mob/living/L = M.loc + L.transferItemToLoc(M, src) + else + M.forceMove(src) + +//common name +/atom/proc/update_multiz(prune_on_fail = FALSE) + return FALSE + +/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(mob/user) + return + +/atom/proc/Bumped(atom/movable/AM) + set waitfor = FALSE + +// Convenience procs to see if a container is open for chemistry handling +/atom/proc/is_open_container() + return is_refillable() && is_drainable() + +/atom/proc/is_injectable(allowmobs = TRUE) + return reagents && (reagents.reagents_holder_flags & (INJECTABLE | REFILLABLE)) + +/atom/proc/is_drawable(allowmobs = TRUE) + return reagents && (reagents.reagents_holder_flags & (DRAWABLE | DRAINABLE)) + +/atom/proc/is_refillable() + return reagents && (reagents.reagents_holder_flags & REFILLABLE) + +/atom/proc/is_drainable() + return reagents && (reagents.reagents_holder_flags & DRAINABLE) + + +/atom/proc/AllowDrop() + return FALSE + +/atom/proc/CheckExit() + return TRUE + +/atom/proc/HasProximity(atom/movable/AM as mob|obj) + return + +/atom/proc/emp_act(severity) + var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) + if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) + wires.emp_pulse() + return protection // Pass the protection value collected here upwards + +/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) + +//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it. +// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first. +/atom/proc/shove_act(mob/living/target, mob/living/user) + return FALSE + +/atom/proc/in_contents_of(container)//can take class or object instance as argument + if(ispath(container)) + if(istype(src.loc, container)) + return TRUE + else if(src in container) + return TRUE + return FALSE + +/atom/proc/update_icon_state() + +/atom/proc/update_overlaysb() + SHOULD_CALL_PARENT(1) + . = list() + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .) + +/atom/proc/update_iconb() + var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) + + if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) + update_icon_state() + + if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS)) + var/list/new_overlays = update_overlaysb() + if(managed_overlays) + cut_overlay(managed_overlays) + managed_overlays = null + if(length(new_overlays)) + managed_overlays = new_overlays + add_overlay(new_overlays) + +/atom/proc/get_examine_name(mob/user) + . = "\a [src]" + var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]") + if(article) + . = "[article] [src]" + override[EXAMINE_POSITION_ARTICLE] = article + + var/should_override = FALSE + + if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) + should_override = TRUE + + + if(blood_DNA && !istype(src, /obj/effect/decal)) + override[EXAMINE_POSITION_BEFORE] = " blood-stained " + should_override = TRUE + + if(should_override) + . = override.Join("") + +///Generate the full examine string of this atom (including icon for goonchat) +/atom/proc/get_examine_string(mob/user, thats = FALSE) + return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]" + +/atom/proc/examine(mob/user) + . = list("[get_examine_string(user, TRUE)].") + + if(desc) + . += desc + + if(reagents) + if(reagents.reagents_holder_flags & TRANSPARENT) + . += "It contains:" + if(length(reagents.reagent_list)) + if(user.can_see_reagents()) //Show each individual reagent + for(var/datum/reagent/R in reagents.reagent_list) + . += "[R.volume] units of [R.name]" + else //Otherwise, just show the total volume + var/total_volume = 0 + for(var/datum/reagent/R in reagents.reagent_list) + total_volume += R.volume + . += "[total_volume] units of various reagents" + else + . += "Nothing." + else if(reagents.reagents_holder_flags & 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(mob/user) + if(buckle_message_cooldown <= world.time) + buckle_message_cooldown = world.time + 50 + to_chat(user, "You can't move while buckled to [src]!") + return + +/atom/proc/prevent_content_explosion() + return FALSE + +/atom/proc/contents_explosion(severity, target) + return //For handling the effects of explosions on contents that would not normally be effected + +/atom/proc/ex_act(severity, target) + set waitfor = FALSE + contents_explosion(severity, target) + SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) + +/atom/proc/blob_act(obj/structure/blob/B) + SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) + return + +/atom/proc/fire_act(exposed_temperature, exposed_volume) + SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) + return + +/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/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube) + return + +//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() + var/blood_id = get_blood_id() + if(!(blood_id in GLOB.blood_reagent_types)) + return + return list("ANIMAL DNA" = "Y-") + +/mob/living/carbon/get_blood_dna_list() + var/blood_id = get_blood_id() + if(!(blood_id in GLOB.blood_reagent_types)) + 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) + // Returns 0 if we have that blood already + var/new_blood_dna = L.get_blood_dna_list() + if(!new_blood_dna) + return FALSE + LAZYINITLIST(blood_DNA) //if our list of DNA doesn't exist yet, initialise it. + var/old_length = blood_DNA.len + blood_DNA |= new_blood_dna + if(blood_DNA.len == old_length) + return FALSE + return TRUE + +//to add blood dna info to the object's blood_DNA list +/atom/proc/transfer_blood_dna(list/blood_dna, list/datum/disease/diseases) + LAZYINITLIST(blood_DNA) + var/old_length = blood_DNA.len + blood_DNA |= blood_dna + if(blood_DNA.len > old_length) + return TRUE + //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 FALSE + return add_blood_DNA(blood_dna, M.diseases) + +//to add blood onto something, with blood dna info to include. +/atom/proc/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + return FALSE + +/obj/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + return transfer_blood_dna(blood_dna, diseases) + +/obj/item/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + . = ..() + if(!.) + return + add_blood_overlay() + +/obj/item/proc/add_blood_overlay() + if(!blood_DNA.len) + return + if(initial(icon) && initial(icon_state)) + 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.Blend(blood_DNA_to_color(), ICON_MULTIPLY) + add_overlay(blood_splatter_icon) + +/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + . = ..() + transfer_blood = rand(2, 4) + +/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src + if(!B) + B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) + B.transfer_blood_dna(blood_dna, diseases) //give blood info to the blood decal. + return TRUE //we bloodied the floor + +/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + if(head) + head.add_blood_DNA(blood_dna, diseases) + update_inv_head() + else if(wear_mask) + wear_mask.add_blood_DNA(blood_dna, diseases) + update_inv_wear_mask() + if(wear_neck) + wear_neck.add_blood_DNA(blood_dna, diseases) + update_inv_neck() + if(wear_suit) + wear_suit.add_blood_DNA(blood_dna, diseases) + update_inv_wear_suit() + else if(w_uniform) + w_uniform.add_blood_DNA(blood_dna, diseases) + update_inv_w_uniform() + if(gloves) + var/obj/item/clothing/gloves/G = gloves + G.add_blood_DNA(blood_dna, diseases) + else + transfer_blood_dna(blood_dna, diseases) + bloody_hands = rand(2, 4) + update_inv_gloves() //handles bloody hands overlays and updating + return TRUE + +/atom/proc/blood_DNA_to_color() + var/list/colors = list()//first we make a list of all bloodtypes present + for(var/bloop in blood_DNA) + if(colors[blood_DNA[bloop]]) + colors[blood_DNA[bloop]]++ + else + colors[blood_DNA[bloop]] = 1 + + var/final_rgb = BLOOD_COLOR_HUMAN //a default so we don't have white blood graphics if something messed up + + if(colors.len) + var/sum = 0 //this is all shitcode, but it works; trust me + final_rgb = bloodtype_to_color(colors[1]) + sum = colors[colors[1]] + if(colors.len > 1) + var/i = 2 + while(i <= colors.len) + var/tmp = colors[colors[i]] + final_rgb = BlendRGB(final_rgb, bloodtype_to_color(colors[i]), tmp/(tmp+sum)) + sum += tmp + i++ + + return final_rgb + +/atom/proc/clean_blood() + . = blood_DNA? TRUE : FALSE + blood_DNA = null + +/atom/proc/wash_cream() + return TRUE + +/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) + +/atom/proc/acid_act(acidpwr, acid_volume) + SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) + +/atom/proc/emag_act() + SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) + +/atom/proc/rad_act(strength) + SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) + +/atom/proc/narsie_act() + SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) + +/atom/proc/ratvar_act() + SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT) + +/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + return FALSE + +/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode) + return FALSE + +/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user) + if(GetComponent(/datum/component/storage)) + return component_storage_contents_dump_act(src_object, user) + return FALSE + +/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user) + var/list/things = src_object.contents() + var/datum/progressbar/progress = new(user, things.len, src) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress))) + stoplag(1) + qdel(progress) + to_chat(user, "You dump as much of [src_object.parent]'s contents into [STR.insert_preposition]to [src] as you can.") + STR.orient2hud(user) + src_object.orient2hud(user) + if(user.active_storage) //refresh the HUD to show the transfered contents + user.active_storage.close(user) + user.active_storage.show_to(user) + return TRUE + +/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) + return null + +//This proc is called on the location of an atom when the atom is Destroy()'d +/atom/proc/handle_atom_del(atom/A) + SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A) + +//called when the turf the atom resides on is ChangeTurfed +/atom/proc/HandleTurfChange(turf/T) + for(var/a in src) + var/atom/A = a + A.HandleTurfChange(T) + +//the vision impairment to give to the mob whose perspective is set to that atom (e.g. an unfocused camera giving you an impaired vision when looking through it) +/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) + 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 + +/atom/proc/mech_melee_attack(obj/mecha/M) + return + +//If a mob logouts/logins in side of an object you can use this proc +/atom/proc/on_log(login) + if(loc) + loc.on_log(login) + + +/* + Atom Colour Priority System + A System that gives finer control over which atom colour to colour the atom with. + The "highest priority" one is always displayed as opposed to the default of + "whichever was set last is displayed" +*/ + + +/* + 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 + +/atom/vv_edit_var(var_name, var_value) + if(!GLOB.Debug2) + flags_1 |= ADMIN_SPAWNED_1 + . = ..() + switch(var_name) + if("color") + add_atom_colour(color, ADMIN_COLOUR_PRIORITY) + +/atom/vv_get_dropdown() + . = ..() + . += "---" + var/turf/curturf = get_turf(src) + if (curturf) + .["Jump to"] = "?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]" + .["Modify Transform"] = "?_src_=vars;[HrefToken()];modtransform=[REF(src)]" + .["Add reagent"] = "?_src_=vars;[HrefToken()];addreagent=[REF(src)]" + .["Trigger EM pulse"] = "?_src_=vars;[HrefToken()];emp=[REF(src)]" + .["Trigger explosion"] = "?_src_=vars;[HrefToken()];explode=[REF(src)]" + +/atom/proc/drop_location() + var/atom/L = loc + if(!L) + return null + return L.AllowDrop() ? L : L.drop_location() + +/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) + +/atom/proc/return_temperature() + return + +// Tool behavior procedure. Redirects to tool-specific procs by default. +// You can override it to catch all tool interactions, for use in complex deconstruction procs. +// Just don't forget to return ..() in the end. +/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) + if(TOOL_ANALYZER) + return analyzer_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 + +/atom/proc/screwdriver_act(mob/living/user, obj/item/I) + SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) + +/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/analyzer_act(mob/living/user, obj/item/I) + return + +/atom/proc/GenerateTag() + return + +// Generic logging helper +/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) + if(!log_globally) + return + + var/log_text = "[key_name(src)] [message] [loc_name(src)]" + switch(message_type) + if(LOG_ATTACK) + log_attack(log_text) + if(LOG_SAY) + log_say(log_text) + if(LOG_WHISPER) + log_whisper(log_text) + if(LOG_EMOTE) + log_emote(log_text) + if(LOG_DSAY) + log_dsay(log_text) + if(LOG_PDA) + log_pda(log_text) + if(LOG_CHAT) + log_chat(log_text) + if(LOG_COMMENT) + log_comment(log_text) + if(LOG_TELECOMMS) + log_telecomms(log_text) + if(LOG_OOC) + log_ooc(log_text) + if(LOG_ADMIN) + log_admin(log_text) + if(LOG_ADMIN_PRIVATE) + log_admin_private(log_text) + if(LOG_ASAY) + log_adminsay(log_text) + if(LOG_OWNERSHIP) + log_game(log_text) + if(LOG_GAME) + log_game(log_text) + else + stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).") + log_game(log_text) + +// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements) +/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null) + var/prefix = tag ? "([tag]) " : "" + var/suffix = forced_by ? " FORCED by [forced_by]" : "" + log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally) + +// Helper for logging of messages with only one sender and receiver +/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag) + if(!tag) + stack_trace("Unspecified tag for private message") + tag = "UNKNOWN" + + source.log_talk(message, message_type, tag="[tag] to [key_name(target)]") + if(source != target) + target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE) + +/* +Proc for attack log creation, because really why not +1 argument is the actor performing the action +2 argument is the target of the action +3 is a verb describing the action (e.g. punched, throwed, kicked, etc.) +4 is a tool with which the action was made (usually an item) +5 is any additional text, which will be appended to the rest of the log line +*/ + +/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null) + var/ssource = key_name(user) + var/starget = key_name(target) + + var/mob/living/living_target = target + var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : "" + + var/sobject = "" + if(object) + sobject = " with [key_name(object)]" + var/saddition = "" + if(addition) + saddition = " [addition]" + + var/postfix = "[sobject][saddition][hp]" + + var/message = "has [what_done] [starget][postfix]" + user.log_message(message, LOG_ATTACK, color="red") + + if(user != target) + var/reverse_message = "has been [what_done] by [ssource][postfix]" + target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE) + +// Filter stuff +/atom/movable/proc/add_filter(name,priority,list/params) + if(!filter_data) + filter_data = list() + var/list/p = params.Copy() + p["priority"] = priority + filter_data[name] = p + update_filters() + +/atom/movable/proc/update_filters() + filters = null + sortTim(filter_data,associative = TRUE) + for(var/f in filter_data) + var/list/data = filter_data[f] + var/list/arguments = data.Copy() + arguments -= "priority" + filters += filter(arglist(arguments)) + +/atom/movable/proc/get_filter(name) + if(filter_data && filter_data[name]) + return filters[filter_data.Find(name)] + +/atom/movable/proc/remove_filter(name) + if(filter_data[name]) + filter_data -= name + update_filters() + return TRUE + +/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) + . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels) diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 3be5e846..b1638844 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -315,6 +315,11 @@ randomdir = 0 duration = 6 +/obj/effect/temp_visual/desynchronizer + name = "desynchronizer field" + icon_state = "chronofield" + duration = 3 + /obj/effect/temp_visual/impact_effect icon_state = "impact_bullet" duration = 5 diff --git a/code/game/say.dm b/code/game/say.dm index 9c2ef0a9..8f7c9704 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -29,7 +29,7 @@ GLOBAL_LIST_INIT(freqtospan, list( send_speech(message, 7, src, , spans, message_language=language) /atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args) /atom/movable/proc/can_speak() return 1 diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index c5c59156..ba762bea 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -49,6 +49,12 @@ A.malf_picker.remove_malf_verbs(A) qdel(A.malf_picker) +/datum/antagonist/traitor/proc/handle_hearing(datum/source, list/hearing_args) + var/message = hearing_args[HEARING_MESSAGE] + message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1") + message = GLOB.syndicate_code_response_regex.Replace(message, "$1") + hearing_args[HEARING_MESSAGE] = message + SSticker.mode.traitors -= owner if(!silent && owner.current) to_chat(owner.current," You are no longer the [special_role]! ") diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm index f1477fb4..22b15ea8 100644 --- a/code/modules/client/client_colour.dm +++ b/code/modules/client/client_colour.dm @@ -111,3 +111,6 @@ /datum/client_colour/monochrome colour = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) priority = INFINITY //we can't see colors anyway! + +/datum/client_colour/monochrome/trance + priority = 1 \ No newline at end of file diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm index 452c8f8b..30c962f9 100644 --- a/code/modules/mob/living/carbon/say.dm +++ b/code/modules/mob/living/carbon/say.dm @@ -17,18 +17,4 @@ if(T) . = T.could_speak_in_language(dt) else - . = initial(dt.flags) & TONGUELESS_SPEECH - -/mob/living/carbon/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(!client) - return - for(var/T in get_traumas()) - var/datum/brain_trauma/trauma = T - message = trauma.on_hear(message, speaker, message_language, raw_message, radio_freq) - - if (src.mind.has_antag_datum(/datum/antagonist/traitor)) - message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1") - message = GLOB.syndicate_code_response_regex.Replace(message, "$1") - - return message + . = initial(dt.flags) & TONGUELESS_SPEECH \ No newline at end of file diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 43293556..2fc3bc0c 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -1,519 +1,519 @@ - -/* EMOTE DATUMS */ -/datum/emote/living - mob_type_allowed_typecache = /mob/living - mob_type_blacklist_typecache = list(/mob/living/simple_animal/slime, /mob/living/brain) - -/datum/emote/living/blush - key = "blush" - key_third_person = "blushes" - message = "blushes." - -/datum/emote/living/bow - key = "bow" - key_third_person = "bows" - message = "bows." - message_param = "bows to %t." - restraint_check = TRUE - -/datum/emote/living/burp - key = "burp" - key_third_person = "burps" - message = "burps." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/choke - key = "choke" - key_third_person = "chokes" - message = "chokes!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/cross - key = "cross" - key_third_person = "crosses" - message = "crosses their arms." - restraint_check = TRUE - -/datum/emote/living/chuckle - key = "chuckle" - key_third_person = "chuckles" - message = "chuckles." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/collapse - key = "collapse" - key_third_person = "collapses" - message = "collapses!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/collapse/run_emote(mob/user, params) - . = ..() - if(. && isliving(user)) - var/mob/living/L = user - L.Unconscious(40) - -/datum/emote/living/cough - key = "cough" - key_third_person = "coughs" - message = "coughs!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE) - . = ..() - if(user.reagents && (user.reagents.get_reagent("menthol") || user.reagents.get_reagent("peppermint_patty"))) - return FALSE - -/datum/emote/living/dance - key = "dance" - key_third_person = "dances" - message = "dances around happily." - restraint_check = TRUE - -/datum/emote/living/deathgasp - key = "deathgasp" - key_third_person = "deathgasps" - message = "seizes up and falls limp, their eyes dead and lifeless..." - message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening." - message_AI = "lets out a flurry of sparks, its screen flickering as its systems slowly halt." - message_alien = "lets out a waning guttural screech, green blood bubbling from its maw..." - message_larva = "lets out a sickly hiss of air and falls limply to the floor..." - message_monkey = "lets out a faint chimper as it collapses and stops moving..." - message_simple = "stops moving..." - stat_allowed = UNCONSCIOUS - -/datum/emote/living/deathgasp/run_emote(mob/user, params) - var/mob/living/simple_animal/S = user - if(istype(S) && S.deathmessage) - message_simple = S.deathmessage - . = ..() - message_simple = initial(message_simple) - if(. && isalienadult(user)) - playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1) - -/datum/emote/living/drool - key = "drool" - key_third_person = "drools" - message = "drools." - -/datum/emote/living/faint - key = "faint" - key_third_person = "faints" - message = "faints." - -/datum/emote/living/faint/run_emote(mob/user, params) - . = ..() - if(. && isliving(user)) - var/mob/living/L = user - L.SetSleeping(200) - -/datum/emote/living/flap - key = "flap" - key_third_person = "flaps" - message = "flaps their wings." - restraint_check = TRUE - var/wing_time = 20 - -/datum/emote/living/flap/run_emote(mob/user, params) - . = ..() - if(. && ishuman(user)) - var/mob/living/carbon/human/H = user - var/open = FALSE - if(H.dna.features["wings"] != "None") - if("wingsopen" in H.dna.species.mutant_bodyparts) - open = TRUE - H.CloseWings() - else - H.OpenWings() - addtimer(CALLBACK(H, open ? /mob/living/carbon/human.proc/OpenWings : /mob/living/carbon/human.proc/CloseWings), wing_time) - -/datum/emote/living/flap/aflap - key = "aflap" - key_third_person = "aflaps" - message = "flaps their wings ANGRILY!" - restraint_check = TRUE - wing_time = 10 - -/datum/emote/living/frown - key = "frown" - key_third_person = "frowns" - message = "frowns." - -/datum/emote/living/gag - key = "gag" - key_third_person = "gags" - message = "gags." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/gasp - key = "gasp" - key_third_person = "gasps" - message = "gasps!" - emote_type = EMOTE_AUDIBLE - stat_allowed = UNCONSCIOUS - -/datum/emote/living/giggle - key = "giggle" - key_third_person = "giggles" - message = "giggles." - message_mime = "giggles silently!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/glare - key = "glare" - key_third_person = "glares" - message = "glares." - message_param = "glares at %t." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/grin - key = "grin" - key_third_person = "grins" - message = "grins." - -/datum/emote/living/groan - key = "groan" - key_third_person = "groans" - message = "groans!" - message_mime = "appears to groan!" - -/datum/emote/living/grimace - key = "grimace" - key_third_person = "grimaces" - message = "grimaces." - -/datum/emote/living/jump - key = "jump" - key_third_person = "jumps" - message = "jumps!" - restraint_check = TRUE - -/datum/emote/living/kiss - key = "kiss" - key_third_person = "kisses" - message = "blows a kiss." - message_param = "blows a kiss to %t." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/laugh - key = "laugh" - key_third_person = "laughs" - message = "laughs." - message_mime = "laughs silently!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE) - . = ..() - if(. && iscarbon(user)) - var/mob/living/carbon/C = user - return !C.silent - -/datum/emote/living/laugh/run_emote(mob/user, params) - . = ..() - if(. && iscarbon(user)) //Citadel Edit because this is hilarious - var/mob/living/carbon/C = user - if(!C.mind || C.mind.miming) - return - if(iscatperson(C)) //we ask for is cat first because they're a subtype that tests true for ishumanbasic because HERESY - playsound(C, pick('sound/voice/catpeople/nyahaha1.ogg', - 'sound/voice/catpeople/nyahaha2.ogg', - 'sound/voice/catpeople/nyaha.ogg', - 'sound/voice/catpeople/nyahehe.ogg'), - 50, 1) - return - if(ishumanbasic(C)) - if(user.gender == FEMALE) - playsound(C, 'sound/voice/human/womanlaugh.ogg', 50, 1) - else - playsound(C, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1) - -/datum/emote/living/look - key = "look" - key_third_person = "looks" - message = "looks." - message_param = "looks at %t." - -/datum/emote/living/nod - key = "nod" - key_third_person = "nods" - message = "nods." - message_param = "nods at %t." - -/datum/emote/living/point - key = "point" - key_third_person = "points" - message = "points." - message_param = "points at %t." - restraint_check = TRUE - -/datum/emote/living/point/run_emote(mob/user, params) - message_param = initial(message_param) // reset - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.get_num_arms() == 0) - if(H.get_num_legs() != 0) - message_param = "tries to point at %t with a leg, falling down in the process!" - H.Knockdown(20) - else - message_param = "bumps [user.p_their()] head on the ground trying to motion towards %t." - H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5) - ..() - -/datum/emote/living/pout - key = "pout" - key_third_person = "pouts" - message = "pouts." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/scream - key = "scream" - key_third_person = "screams" - message = "screams." - message_mime = "acts out a scream!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/scowl - key = "scowl" - key_third_person = "scowls" - message = "scowls." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/shake - key = "shake" - key_third_person = "shakes" - message = "shakes their head." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/shiver - key = "shiver" - key_third_person = "shiver" - message = "shivers." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/sigh - key = "sigh" - key_third_person = "sighs" - message = "sighs." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/sit - key = "sit" - key_third_person = "sits" - message = "sits down." - -/datum/emote/living/smile - key = "smile" - key_third_person = "smiles" - message = "smiles." - -/datum/emote/living/sneeze - key = "sneeze" - key_third_person = "sneezes" - message = "sneezes." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/smug - key = "smug" - key_third_person = "smugs" - message = "grins smugly." - -/datum/emote/living/sniff - key = "sniff" - key_third_person = "sniffs" - message = "sniffs." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/snore - key = "snore" - key_third_person = "snores" - message = "snores." - message_mime = "sleeps soundly." - emote_type = EMOTE_AUDIBLE - stat_allowed = UNCONSCIOUS - -/datum/emote/living/stare - key = "stare" - key_third_person = "stares" - message = "stares." - message_param = "stares at %t." - -/datum/emote/living/strech - key = "stretch" - key_third_person = "stretches" - message = "stretches their arms." - -/datum/emote/living/sulk - key = "sulk" - key_third_person = "sulks" - message = "sulks down sadly." - -/datum/emote/living/surrender - key = "surrender" - key_third_person = "surrenders" - message = "puts their hands on their head and falls to the ground, they surrender!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/surrender/run_emote(mob/user, params) - . = ..() - if(. && isliving(user)) - var/mob/living/L = user - L.Knockdown(200) - -/datum/emote/living/sway - key = "sway" - key_third_person = "sways" - message = "sways around dizzily." - -/datum/emote/living/tremble - key = "tremble" - key_third_person = "trembles" - message = "trembles in fear!" - -/datum/emote/living/twitch - key = "twitch" - key_third_person = "twitches" - message = "twitches violently." - -/datum/emote/living/twitch_s - key = "twitch_s" - message = "twitches." - -/datum/emote/living/wave - key = "wave" - key_third_person = "waves" - message = "waves." - -/datum/emote/living/whimper - key = "whimper" - key_third_person = "whimpers" - message = "whimpers." - message_mime = "appears hurt." - -/datum/emote/living/wsmile - key = "wsmile" - key_third_person = "wsmiles" - message = "smiles weakly." - -/datum/emote/living/yawn - key = "yawn" - key_third_person = "yawns" - message = "yawns." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/custom - key = "me" - key_third_person = "custom" - message = null - -/datum/emote/living/custom/proc/check_invalid(mob/user, input) - . = TRUE - if(copytext(input,1,5) == "says") - to_chat(user, "Invalid emote.") - else if(copytext(input,1,9) == "exclaims") - to_chat(user, "Invalid emote.") - else if(copytext(input,1,6) == "yells") - to_chat(user, "Invalid emote.") - else if(copytext(input,1,5) == "asks") - to_chat(user, "Invalid emote.") - else - . = FALSE - -/datum/emote/living/custom/run_emote(mob/user, params, type_override = null) - if(jobban_isbanned(user, "emote")) - to_chat(user, "You cannot send custom emotes (banned).") - return FALSE - else if(QDELETED(user)) - return FALSE - else if(user.client && user.client.prefs.muted & MUTE_IC) - to_chat(user, "You cannot send IC messages (muted).") - return FALSE - else if(!params) - var/custom_emote = copytext(sanitize(input("Choose an emote to display.") as message|null), 1, MAX_MESSAGE_LEN) //CIT CHANGE - expands emote textbox - if(custom_emote && !check_invalid(user, custom_emote)) - var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable") - switch(type) - if("Visible") - emote_type = EMOTE_VISIBLE - if("Hearable") - emote_type = EMOTE_AUDIBLE - else - alert("Unable to use this emote, must be either hearable or visible.") - return - message = custom_emote - else - message = params - if(type_override) - emote_type = type_override - message = user.say_emphasis(message) - . = ..() - message = null - emote_type = EMOTE_VISIBLE - -/datum/emote/living/custom/replace_pronoun(mob/user, message) - return message - -/datum/emote/living/help - key = "help" - -/datum/emote/living/help/run_emote(mob/user, params) - var/list/keys = list() - var/list/message = list("Available emotes, you can use them with say \"*emote\": ") - - var/datum/emote/E - var/list/emote_list = E.emote_list - for(var/e in emote_list) - if(e in keys) - continue - E = emote_list[e] - if(E.can_run_emote(user, status_check = FALSE)) - keys += E.key - - keys = sortList(keys) - - for(var/emote in keys) - if(LAZYLEN(message) > 1) - message += ", [emote]" - else - message += "[emote]" - - message += "." - - message = jointext(message, "") - - to_chat(user, message) - -/datum/emote/sound/beep - key = "beep" - key_third_person = "beeps" - message = "beeps." - message_param = "beeps at %t." - sound = 'sound/machines/twobeep.ogg' - mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/carbon/human) - -/datum/emote/living/circle - key = "circle" - key_third_person = "circles" - restraint_check = TRUE - -/datum/emote/living/circle/run_emote(mob/user, params) - . = ..() - var/obj/item/circlegame/N = new(user) - if(user.put_in_hands(N)) - to_chat(user, "You make a circle with your hand.") - else - qdel(N) - to_chat(user, "You don't have any free hands to make a circle with.") - -/datum/emote/living/slap - key = "slap" - key_third_person = "slaps" - restraint_check = TRUE - -/datum/emote/living/slap/run_emote(mob/user, params) - . = ..() - if(!.) - return - var/obj/item/slapper/N = new(user) - if(user.put_in_hands(N)) - to_chat(user, "You ready your slapping hand.") - else - to_chat(user, "You're incapable of slapping in your current state.") + +/* EMOTE DATUMS */ +/datum/emote/living + mob_type_allowed_typecache = /mob/living + mob_type_blacklist_typecache = list(/mob/living/simple_animal/slime, /mob/living/brain) + +/datum/emote/living/blush + key = "blush" + key_third_person = "blushes" + message = "blushes." + +/datum/emote/living/bow + key = "bow" + key_third_person = "bows" + message = "bows." + message_param = "bows to %t." + restraint_check = TRUE + +/datum/emote/living/burp + key = "burp" + key_third_person = "burps" + message = "burps." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/choke + key = "choke" + key_third_person = "chokes" + message = "chokes!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/cross + key = "cross" + key_third_person = "crosses" + message = "crosses their arms." + restraint_check = TRUE + +/datum/emote/living/chuckle + key = "chuckle" + key_third_person = "chuckles" + message = "chuckles." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/collapse + key = "collapse" + key_third_person = "collapses" + message = "collapses!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/collapse/run_emote(mob/user, params) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.Unconscious(40) + +/datum/emote/living/cough + key = "cough" + key_third_person = "coughs" + message = "coughs!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional) + . = ..() + if(HAS_TRAIT(user, TRAIT_SOOTHED_THROAT)) + return FALSE + +/datum/emote/living/dance + key = "dance" + key_third_person = "dances" + message = "dances around happily." + restraint_check = TRUE + +/datum/emote/living/deathgasp + key = "deathgasp" + key_third_person = "deathgasps" + message = "seizes up and falls limp, their eyes dead and lifeless..." + message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening." + message_AI = "lets out a flurry of sparks, its screen flickering as its systems slowly halt." + message_alien = "lets out a waning guttural screech, green blood bubbling from its maw..." + message_larva = "lets out a sickly hiss of air and falls limply to the floor..." + message_monkey = "lets out a faint chimper as it collapses and stops moving..." + message_simple = "stops moving..." + stat_allowed = UNCONSCIOUS + +/datum/emote/living/deathgasp/run_emote(mob/user, params) + var/mob/living/simple_animal/S = user + if(istype(S) && S.deathmessage) + message_simple = S.deathmessage + . = ..() + message_simple = initial(message_simple) + if(. && isalienadult(user)) + playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1) + +/datum/emote/living/drool + key = "drool" + key_third_person = "drools" + message = "drools." + +/datum/emote/living/faint + key = "faint" + key_third_person = "faints" + message = "faints." + +/datum/emote/living/faint/run_emote(mob/user, params) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.SetSleeping(200) + +/datum/emote/living/flap + key = "flap" + key_third_person = "flaps" + message = "flaps their wings." + restraint_check = TRUE + var/wing_time = 20 + +/datum/emote/living/flap/run_emote(mob/user, params) + . = ..() + if(. && ishuman(user)) + var/mob/living/carbon/human/H = user + var/open = FALSE + if(H.dna.features["wings"] != "None") + if("wingsopen" in H.dna.species.mutant_bodyparts) + open = TRUE + H.CloseWings() + else + H.OpenWings() + addtimer(CALLBACK(H, open ? /mob/living/carbon/human.proc/OpenWings : /mob/living/carbon/human.proc/CloseWings), wing_time) + +/datum/emote/living/flap/aflap + key = "aflap" + key_third_person = "aflaps" + message = "flaps their wings ANGRILY!" + restraint_check = TRUE + wing_time = 10 + +/datum/emote/living/frown + key = "frown" + key_third_person = "frowns" + message = "frowns." + +/datum/emote/living/gag + key = "gag" + key_third_person = "gags" + message = "gags." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/gasp + key = "gasp" + key_third_person = "gasps" + message = "gasps!" + emote_type = EMOTE_AUDIBLE + stat_allowed = UNCONSCIOUS + +/datum/emote/living/giggle + key = "giggle" + key_third_person = "giggles" + message = "giggles." + message_mime = "giggles silently!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/glare + key = "glare" + key_third_person = "glares" + message = "glares." + message_param = "glares at %t." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/grin + key = "grin" + key_third_person = "grins" + message = "grins." + +/datum/emote/living/groan + key = "groan" + key_third_person = "groans" + message = "groans!" + message_mime = "appears to groan!" + +/datum/emote/living/grimace + key = "grimace" + key_third_person = "grimaces" + message = "grimaces." + +/datum/emote/living/jump + key = "jump" + key_third_person = "jumps" + message = "jumps!" + restraint_check = TRUE + +/datum/emote/living/kiss + key = "kiss" + key_third_person = "kisses" + message = "blows a kiss." + message_param = "blows a kiss to %t." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/laugh + key = "laugh" + key_third_person = "laughs" + message = "laughs." + message_mime = "laughs silently!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE) + . = ..() + if(. && iscarbon(user)) + var/mob/living/carbon/C = user + return !C.silent + +/datum/emote/living/laugh/run_emote(mob/user, params) + . = ..() + if(. && iscarbon(user)) //Citadel Edit because this is hilarious + var/mob/living/carbon/C = user + if(!C.mind || C.mind.miming) + return + if(iscatperson(C)) //we ask for is cat first because they're a subtype that tests true for ishumanbasic because HERESY + playsound(C, pick('sound/voice/catpeople/nyahaha1.ogg', + 'sound/voice/catpeople/nyahaha2.ogg', + 'sound/voice/catpeople/nyaha.ogg', + 'sound/voice/catpeople/nyahehe.ogg'), + 50, 1) + return + if(ishumanbasic(C)) + if(user.gender == FEMALE) + playsound(C, 'sound/voice/human/womanlaugh.ogg', 50, 1) + else + playsound(C, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1) + +/datum/emote/living/look + key = "look" + key_third_person = "looks" + message = "looks." + message_param = "looks at %t." + +/datum/emote/living/nod + key = "nod" + key_third_person = "nods" + message = "nods." + message_param = "nods at %t." + +/datum/emote/living/point + key = "point" + key_third_person = "points" + message = "points." + message_param = "points at %t." + restraint_check = TRUE + +/datum/emote/living/point/run_emote(mob/user, params) + message_param = initial(message_param) // reset + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.get_num_arms() == 0) + if(H.get_num_legs() != 0) + message_param = "tries to point at %t with a leg, falling down in the process!" + H.Knockdown(20) + else + message_param = "bumps [user.p_their()] head on the ground trying to motion towards %t." + H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5) + ..() + +/datum/emote/living/pout + key = "pout" + key_third_person = "pouts" + message = "pouts." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/scream + key = "scream" + key_third_person = "screams" + message = "screams." + message_mime = "acts out a scream!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/scowl + key = "scowl" + key_third_person = "scowls" + message = "scowls." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/shake + key = "shake" + key_third_person = "shakes" + message = "shakes their head." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/shiver + key = "shiver" + key_third_person = "shiver" + message = "shivers." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/sigh + key = "sigh" + key_third_person = "sighs" + message = "sighs." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/sit + key = "sit" + key_third_person = "sits" + message = "sits down." + +/datum/emote/living/smile + key = "smile" + key_third_person = "smiles" + message = "smiles." + +/datum/emote/living/sneeze + key = "sneeze" + key_third_person = "sneezes" + message = "sneezes." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/smug + key = "smug" + key_third_person = "smugs" + message = "grins smugly." + +/datum/emote/living/sniff + key = "sniff" + key_third_person = "sniffs" + message = "sniffs." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/snore + key = "snore" + key_third_person = "snores" + message = "snores." + message_mime = "sleeps soundly." + emote_type = EMOTE_AUDIBLE + stat_allowed = UNCONSCIOUS + +/datum/emote/living/stare + key = "stare" + key_third_person = "stares" + message = "stares." + message_param = "stares at %t." + +/datum/emote/living/strech + key = "stretch" + key_third_person = "stretches" + message = "stretches their arms." + +/datum/emote/living/sulk + key = "sulk" + key_third_person = "sulks" + message = "sulks down sadly." + +/datum/emote/living/surrender + key = "surrender" + key_third_person = "surrenders" + message = "puts their hands on their head and falls to the ground, they surrender!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/surrender/run_emote(mob/user, params) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.Knockdown(200) + +/datum/emote/living/sway + key = "sway" + key_third_person = "sways" + message = "sways around dizzily." + +/datum/emote/living/tremble + key = "tremble" + key_third_person = "trembles" + message = "trembles in fear!" + +/datum/emote/living/twitch + key = "twitch" + key_third_person = "twitches" + message = "twitches violently." + +/datum/emote/living/twitch_s + key = "twitch_s" + message = "twitches." + +/datum/emote/living/wave + key = "wave" + key_third_person = "waves" + message = "waves." + +/datum/emote/living/whimper + key = "whimper" + key_third_person = "whimpers" + message = "whimpers." + message_mime = "appears hurt." + +/datum/emote/living/wsmile + key = "wsmile" + key_third_person = "wsmiles" + message = "smiles weakly." + +/datum/emote/living/yawn + key = "yawn" + key_third_person = "yawns" + message = "yawns." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/custom + key = "me" + key_third_person = "custom" + message = null + +/datum/emote/living/custom/proc/check_invalid(mob/user, input) + . = TRUE + if(copytext(input,1,5) == "says") + to_chat(user, "Invalid emote.") + else if(copytext(input,1,9) == "exclaims") + to_chat(user, "Invalid emote.") + else if(copytext(input,1,6) == "yells") + to_chat(user, "Invalid emote.") + else if(copytext(input,1,5) == "asks") + to_chat(user, "Invalid emote.") + else + . = FALSE + +/datum/emote/living/custom/run_emote(mob/user, params, type_override = null) + if(jobban_isbanned(user, "emote")) + to_chat(user, "You cannot send custom emotes (banned).") + return FALSE + else if(QDELETED(user)) + return FALSE + else if(user.client && user.client.prefs.muted & MUTE_IC) + to_chat(user, "You cannot send IC messages (muted).") + return FALSE + else if(!params) + var/custom_emote = copytext(sanitize(input("Choose an emote to display.") as message|null), 1, MAX_MESSAGE_LEN) //CIT CHANGE - expands emote textbox + if(custom_emote && !check_invalid(user, custom_emote)) + var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable") + switch(type) + if("Visible") + emote_type = EMOTE_VISIBLE + if("Hearable") + emote_type = EMOTE_AUDIBLE + else + alert("Unable to use this emote, must be either hearable or visible.") + return + message = custom_emote + else + message = params + if(type_override) + emote_type = type_override + message = user.say_emphasis(message) + . = ..() + message = null + emote_type = EMOTE_VISIBLE + +/datum/emote/living/custom/replace_pronoun(mob/user, message) + return message + +/datum/emote/living/help + key = "help" + +/datum/emote/living/help/run_emote(mob/user, params) + var/list/keys = list() + var/list/message = list("Available emotes, you can use them with say \"*emote\": ") + + var/datum/emote/E + var/list/emote_list = E.emote_list + for(var/e in emote_list) + if(e in keys) + continue + E = emote_list[e] + if(E.can_run_emote(user, status_check = FALSE)) + keys += E.key + + keys = sortList(keys) + + for(var/emote in keys) + if(LAZYLEN(message) > 1) + message += ", [emote]" + else + message += "[emote]" + + message += "." + + message = jointext(message, "") + + to_chat(user, message) + +/datum/emote/sound/beep + key = "beep" + key_third_person = "beeps" + message = "beeps." + message_param = "beeps at %t." + sound = 'sound/machines/twobeep.ogg' + mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/carbon/human) + +/datum/emote/living/circle + key = "circle" + key_third_person = "circles" + restraint_check = TRUE + +/datum/emote/living/circle/run_emote(mob/user, params) + . = ..() + var/obj/item/circlegame/N = new(user) + if(user.put_in_hands(N)) + to_chat(user, "You make a circle with your hand.") + else + qdel(N) + to_chat(user, "You don't have any free hands to make a circle with.") + +/datum/emote/living/slap + key = "slap" + key_third_person = "slaps" + restraint_check = TRUE + +/datum/emote/living/slap/run_emote(mob/user, params) + . = ..() + if(!.) + return + var/obj/item/slapper/N = new(user) + if(user.put_in_hands(N)) + to_chat(user, "You ready your slapping hand.") + else + to_chat(user, "You're incapable of slapping in your current state.") diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 0cc469dd..a9f9bb48 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -1743,6 +1743,7 @@ datum/reagent/consumable/ethanol/creme_de_coconut value = 2 /datum/reagent/consumable/ethanol/peppermint_patty/on_mob_life(mob/living/carbon/M) + M.apply_status_effect(/datum/status_effect/throat_soothed) M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL) ..() diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index b27d66e2..55356066 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -760,6 +760,10 @@ glass_name = "glass of menthol" glass_desc = "Tastes naturally minty, and imparts a very mild numbing sensation." +/datum/reagent/consumable/menthol/on_mob_life(mob/living/L) + L.apply_status_effect(/datum/status_effect/throat_soothed) + ..() + /datum/reagent/consumable/grenadine name = "Grenadine" description = "Not cherry flavored!" diff --git a/code/modules/research/designs/bluespace_designs.dm b/code/modules/research/designs/bluespace_designs.dm index b7a201f5..ec4309b6 100644 --- a/code/modules/research/designs/bluespace_designs.dm +++ b/code/modules/research/designs/bluespace_designs.dm @@ -1,76 +1,86 @@ - -///////////////////////////////////////// -//////////////Blue Space///////////////// -///////////////////////////////////////// - -/datum/design/beacon - name = "Tracking Beacon" - desc = "A blue space tracking beacon." - id = "beacon" - build_type = PROTOLATHE - materials = list(MAT_METAL = 150, MAT_GLASS = 100) - build_path = /obj/item/beacon - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SECURITY - -/datum/design/bag_holding - name = "Bag of Holding" - desc = "A backpack that opens into a localized pocket of bluespace." - id = "bag_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) - build_path = /obj/item/storage/backpack/holding - category = list("Bluespace Designs") - dangerous_construction = TRUE - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/satchel_holding - name = "Satchel of Holding" - desc = "A satchel that opens into a localized pocket of bluespace." - id = "satchel_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) - build_path = /obj/item/storage/backpack/holding/satchel - category = list("Bluespace Designs") - dangerous_construction = TRUE - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/biobag_holding - name = "Bio Bag of Holding" - desc = "A chemical holding thingy. Mostly used for xenobiology." - id = "biobag_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 1500, MAT_DIAMOND = 750, MAT_URANIUM = 250, MAT_BLUESPACE = 1000) - build_path = /obj/item/storage/bag/bio/holding - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/bluespace_crystal - name = "Artificial Bluespace Crystal" - desc = "A small blue crystal with mystical properties." - id = "bluespace_crystal" - build_type = PROTOLATHE - materials = list(MAT_DIAMOND = 1500, MAT_PLASMA = 1500) - build_path = /obj/item/stack/ore/bluespace_crystal/artificial - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/telesci_gps - name = "GPS Device" - desc = "Little thingie that can track its position at all times." - id = "telesci_gps" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 1000) - build_path = /obj/item/gps - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO - -/datum/design/miningsatchel_holding - name = "Mining Satchel of Holding" - desc = "A mining satchel that can hold an infinite amount of ores." - id = "minerbag_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 250, MAT_URANIUM = 500) //quite cheap, for more convenience - build_path = /obj/item/storage/bag/ore/holding - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO + +///////////////////////////////////////// +//////////////Blue Space///////////////// +///////////////////////////////////////// + +/datum/design/beacon + name = "Tracking Beacon" + desc = "A blue space tracking beacon." + id = "beacon" + build_type = PROTOLATHE + materials = list(MAT_METAL = 150, MAT_GLASS = 100) + build_path = /obj/item/beacon + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SECURITY + +/datum/design/bag_holding + name = "Bag of Holding" + desc = "A backpack that opens into a localized pocket of bluespace." + id = "bag_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) + build_path = /obj/item/storage/backpack/holding + category = list("Bluespace Designs") + dangerous_construction = TRUE + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/satchel_holding + name = "Satchel of Holding" + desc = "A satchel that opens into a localized pocket of bluespace." + id = "satchel_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) + build_path = /obj/item/storage/backpack/holding/satchel + category = list("Bluespace Designs") + dangerous_construction = TRUE + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/biobag_holding + name = "Bio Bag of Holding" + desc = "A chemical holding thingy. Mostly used for xenobiology." + id = "biobag_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 1500, MAT_DIAMOND = 750, MAT_URANIUM = 250, MAT_BLUESPACE = 1000) + build_path = /obj/item/storage/bag/bio/holding + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/bluespace_crystal + name = "Artificial Bluespace Crystal" + desc = "A small blue crystal with mystical properties." + id = "bluespace_crystal" + build_type = PROTOLATHE + materials = list(MAT_DIAMOND = 1500, MAT_PLASMA = 1500) + build_path = /obj/item/stack/ore/bluespace_crystal/artificial + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/telesci_gps + name = "GPS Device" + desc = "Little thingie that can track its position at all times." + id = "telesci_gps" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 1000) + build_path = /obj/item/gps + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO + +/datum/design/desynchronizer + name = "Desynchronizer" + desc = "A device that can desynchronize the user from spacetime." + id = "desynchronizer" + build_type = PROTOLATHE + materials = list(MAT_METAL = 1000, MAT_GLASS = 500, MAT_SILVER = 1500, MAT_BLUESPACE = 1000) + build_path = /obj/item/desynchronizer + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/miningsatchel_holding + name = "Mining Satchel of Holding" + desc = "A mining satchel that can hold an infinite amount of ores." + id = "minerbag_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 250, MAT_URANIUM = 500) //quite cheap, for more convenience + build_path = /obj/item/storage/bag/ore/holding + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO diff --git a/code/modules/research/nanites/nanite_programs.dm b/code/modules/research/nanites/nanite_programs.dm index a06311f2..2a6563fe 100644 --- a/code/modules/research/nanites/nanite_programs.dm +++ b/code/modules/research/nanites/nanite_programs.dm @@ -208,9 +208,6 @@ datum/nanite_program/proc/on_mob_remove() /datum/nanite_program/proc/on_death() return -/datum/nanite_program/proc/on_hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - return - /datum/nanite_program/proc/software_error(type) if(!type) type = rand(1,5) diff --git a/code/modules/research/nanites/nanite_programs/sensor.dm b/code/modules/research/nanites/nanite_programs/sensor.dm index d494ec8c..535b92c6 100644 --- a/code/modules/research/nanites/nanite_programs/sensor.dm +++ b/code/modules/research/nanites/nanite_programs/sensor.dm @@ -345,6 +345,14 @@ var/sentence = "" var/inclusive = TRUE + +/datum/nanite_program/sensor/voice/on_mob_add() + . = ..() + RegisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear) + +/datum/nanite_program/sensor/voice/on_mob_remove() + UnregisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear) + /datum/nanite_program/sensor/voice/set_extra_setting(user, setting) if(setting == "Sent Code") var/new_code = input(user, "Set the sent code (1-9999):", name, null) as null|num @@ -378,15 +386,12 @@ target.sentence = sentence target.inclusive = inclusive -/datum/nanite_program/sensor/voice/on_hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) +/datum/nanite_program/sensor/voice/proc/on_hear(datum/source, list/hearing_args) if(!sentence) return - //To make it not case sensitive - var/low_message = lowertext(raw_message) - var/low_sentence = lowertext(sentence) if(inclusive) - if(findtext(low_message, low_sentence)) + if(findtextEx(hearing_args[HEARING_RAW_MESSAGE], sentence)) send_code() else - if(low_message == low_sentence) + if(hearing_args[HEARING_RAW_MESSAGE] == sentence) send_code() \ No newline at end of file diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index a13739e3..0a518774 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -323,6 +323,15 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) export_price = 5000 +/datum/techweb_node/unregulated_bluespace + id = "unregulated_bluespace" + display_name = "Unregulated Bluespace Research" + description = "Bluespace technology using unstable or unbalanced procedures, prone to damaging the fabric of bluespace. Outlawed by galactic conventions." + prereq_ids = list("bluespace_warping", "syndicate_basic") + design_ids = list("desynchronizer") + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + export_price = 2500 + /////////////////////////plasma tech///////////////////////// /datum/techweb_node/basic_plasma id = "basic_plasma" diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index 397d2ce57b44aa0746570cb79456d6d370873e15..fef57630e29c20ef251f141154b59e66290ed380 100644 GIT binary patch delta 21311 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 diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 9a9a4017d16fc08dec2d672899f78d32142eec4c..37a1a0877b3e289289169ac2467e273f039a0272 100644 GIT binary patch delta 14109 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 X7XQnC>JGz8fN*K5>8KXpvkLuRP?TA1 diff --git a/tgstation.dme b/tgstation.dme index bd08e84b..5cebfa8f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -890,6 +890,7 @@ #include "code\game\objects\items\devices\camera_bug.dm" #include "code\game\objects\items\devices\chameleonproj.dm" #include "code\game\objects\items\devices\compressionkit.dm" +#include "code\game\objects\items\devices\desynchronizer.dm" #include "code\game\objects\items\devices\dogborg_sleeper.dm" #include "code\game\objects\items\devices\doorCharge.dm" #include "code\game\objects\items\devices\electroadaptive_pseudocircuit.dm" From 0db5aed9864cda6cf9c2f6968c7a419dd712177c Mon Sep 17 00:00:00 2001 From: Putnam3145 Date: Thu, 5 Dec 2019 21:16:47 -0800 Subject: [PATCH 06/21] Merge pull request #10056 from Ghommie/Ghommie-cit430 Ports "Slightly improves visible and audible messages." --- code/__DEFINES/combat.dm | 409 +-- code/__DEFINES/say.dm | 4 + code/datums/components/storage/storage.dm | 8 +- code/game/objects/items/devices/PDA/PDA.dm | 2 +- .../objects/items/devices/PDA/virus_cart.dm | 4 +- code/game/objects/items/grenades/flashbang.dm | 72 +- code/game/objects/items/plushes.dm | 8 +- code/game/objects/items/storage/boxes.dm | 2716 ++++++++--------- code/game/objects/items/teleportation.dm | 480 +-- code/game/objects/items/toys.dm | 2 +- code/game/objects/structures/loom.dm | 137 +- code/modules/clothing/spacesuits/hardsuit.dm | 2 +- code/modules/hydroponics/grown/cotton.dm | 2 +- code/modules/hydroponics/grown/pumpkin.dm | 4 +- code/modules/hydroponics/grown/towercap.dm | 2 +- .../carbon/human/species_types/golems.dm | 8 +- code/modules/mob/living/say.dm | 862 +++--- code/modules/mob/living/silicon/ai/ai.dm | 2002 ++++++------ .../simple_animal/hostile/megafauna/drake.dm | 2 +- code/modules/mob/mob.dm | 158 +- code/modules/mob/say_vr.dm | 4 +- code/modules/power/supermatter/supermatter.dm | 6 +- code/modules/projectiles/pins.dm | 4 +- code/modules/recycling/disposal/holder.dm | 2 +- .../code/modules/vore/eating/belly_obj_vr.dm | 2 +- 25 files changed, 3475 insertions(+), 3427 deletions(-) diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 8acc1d8f..e9d90e23 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -1,204 +1,205 @@ -/*ALL DEFINES RELATED TO COMBAT GO HERE*/ - -//Damage and status effect defines - -//Damage defines //TODO: merge these down to reduce on defines -#define BRUTE "brute" -#define BURN "fire" -#define TOX "tox" -#define OXY "oxy" -#define CLONE "clone" -#define STAMINA "stamina" -#define BRAIN "brain" - -//bitflag damage defines used for suicide_act -#define BRUTELOSS (1<<0) -#define FIRELOSS (1<<1) -#define TOXLOSS (1<<2) -#define OXYLOSS (1<<3) -#define SHAME (1<<4) -#define MANUAL_SUICIDE (1<<5) //suicide_act will do the actual killing. - -#define EFFECT_STUN "stun" -#define EFFECT_KNOCKDOWN "knockdown" -#define EFFECT_UNCONSCIOUS "unconscious" -#define EFFECT_IRRADIATE "irradiate" -#define EFFECT_STUTTER "stutter" -#define EFFECT_SLUR "slur" -#define EFFECT_EYE_BLUR "eye_blur" -#define EFFECT_DROWSY "drowsy" -#define EFFECT_JITTER "jitter" - -//Bitflags defining which status effects could be or are inflicted on a mob -#define CANSTUN (1<<0) -#define CANKNOCKDOWN (1<<1) -#define CANUNCONSCIOUS (1<<2) -#define CANPUSH (1<<3) -#define GODMODE (1<<4) - -//Health Defines -#define HEALTH_THRESHOLD_CRIT 0 -#define HEALTH_THRESHOLD_FULLCRIT -30 -#define HEALTH_THRESHOLD_DEAD -100 - -//Actual combat defines - -//click cooldowns, in tenths of a second, used for various combat actions -#define CLICK_CD_MELEE 8 -#define CLICK_CD_RANGE 4 -#define CLICK_CD_RAPID 2 -#define CLICK_CD_CLICK_ABILITY 6 -#define CLICK_CD_BREAKOUT 100 -#define CLICK_CD_HANDCUFFED 10 -#define CLICK_CD_RESIST 20 -#define CLICK_CD_GRABBING 10 - -//Cuff resist speeds -#define FAST_CUFFBREAK 1 -#define INSTANT_CUFFBREAK 2 - -//Grab levels -#define GRAB_PASSIVE 0 -#define GRAB_AGGRESSIVE 1 -#define GRAB_NECK 2 -#define GRAB_KILL 3 - -//slowdown when in softcrit -#define SOFTCRIT_ADD_SLOWDOWN 6 - -//Attack types for checking shields/hit reactions -#define MELEE_ATTACK 1 -#define UNARMED_ATTACK 2 -#define PROJECTILE_ATTACK 3 -#define THROWN_PROJECTILE_ATTACK 4 -#define LEAP_ATTACK 5 - -//attack visual effects -#define ATTACK_EFFECT_PUNCH "punch" -#define ATTACK_EFFECT_KICK "kick" -#define ATTACK_EFFECT_SMASH "smash" -#define ATTACK_EFFECT_CLAW "claw" -#define ATTACK_EFFECT_DISARM "disarm" -#define ATTACK_EFFECT_ASS_SLAP "ass_slap" -#define ATTACK_EFFECT_FACE_SLAP "face_slap" -#define ATTACK_EFFECT_BITE "bite" -#define ATTACK_EFFECT_MECHFIRE "mech_fire" -#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" -#define ATTACK_EFFECT_BOOP "boop" //Honk - -//intent defines -#define INTENT_HELP "help" -#define INTENT_GRAB "grab" -#define INTENT_DISARM "disarm" -#define INTENT_HARM "harm" -//NOTE: INTENT_HOTKEY_* defines are not actual intents! -//they are here to support hotkeys -#define INTENT_HOTKEY_LEFT "left" -#define INTENT_HOTKEY_RIGHT "right" - -//the define for visible message range in combat -#define COMBAT_MESSAGE_RANGE 3 - -//Shove knockdown lengths (deciseconds) -#define SHOVE_KNOCKDOWN_SOLID 30 -#define SHOVE_KNOCKDOWN_HUMAN 30 -#define SHOVE_KNOCKDOWN_TABLE 30 -#define SHOVE_KNOCKDOWN_COLLATERAL 10 -//Shove slowdown -#define SHOVE_SLOWDOWN_ID "shove_slowdown" -#define SHOVE_SLOWDOWN_LENGTH 30 -#define SHOVE_SLOWDOWN_STRENGTH 0.85 //multiplier -//Shove disarming item list -GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( - /obj/item/gun))) - - -//Combat object defines - -//Embedded objects -#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) -#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) -#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) -#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) -#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) -#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) -#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) -#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) -#define EMBEDDED_UNSAFE_REMOVAL_TIME 150 //A Time in ticks, total removal time = (this/item.w_class) - -//Gun weapon weight -#define WEAPON_LIGHT 1 -#define WEAPON_MEDIUM 2 -#define WEAPON_HEAVY 3 -//Gun trigger guards -#define TRIGGER_GUARD_ALLOW_ALL -1 -#define TRIGGER_GUARD_NONE 0 -#define TRIGGER_GUARD_NORMAL 1 -//E-gun self-recharge values -#define EGUN_NO_SELFCHARGE 0 -#define EGUN_SELFCHARGE 1 -#define EGUN_SELFCHARGE_BORG 2 - -//Object/Item sharpness -#define IS_BLUNT 0 -#define IS_SHARP 1 -#define IS_SHARP_ACCURATE 2 - -//His Grace. -#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. -#define HIS_GRACE_PECKISH 20 //Slightly hungry. -#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. -#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. -#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. -#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. -#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. - -#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. - -#define EXPLODE_NONE 0 //Don't even ask me why we need this. -#define EXPLODE_DEVASTATE 1 -#define EXPLODE_HEAVY 2 -#define EXPLODE_LIGHT 3 - -#define EMP_HEAVY 1 -#define EMP_LIGHT 2 - -#define GRENADE_CLUMSY_FUMBLE 1 -#define GRENADE_NONCLUMSY_FUMBLE 2 -#define GRENADE_NO_FUMBLE 3 - -#define BODY_ZONE_HEAD "head" -#define BODY_ZONE_CHEST "chest" -#define BODY_ZONE_L_ARM "l_arm" -#define BODY_ZONE_R_ARM "r_arm" -#define BODY_ZONE_L_LEG "l_leg" -#define BODY_ZONE_R_LEG "r_leg" - -#define BODY_ZONE_PRECISE_EYES "eyes" -#define BODY_ZONE_PRECISE_MOUTH "mouth" -#define BODY_ZONE_PRECISE_GROIN "groin" -#define BODY_ZONE_PRECISE_L_HAND "l_hand" -#define BODY_ZONE_PRECISE_R_HAND "r_hand" -#define BODY_ZONE_PRECISE_L_FOOT "l_foot" -#define BODY_ZONE_PRECISE_R_FOOT "r_foot" - -//We will round to this value in damage calculations. -#define DAMAGE_PRECISION 0.1 - -//bullet_act() return values -#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. -#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. - -//items total mass, used to calculate their attacks' stamina costs. If not defined, the cost will be (w_class * 1.25) -#define TOTAL_MASS_TINY_ITEM 1.25 -#define TOTAL_MASS_SMALL_ITEM 2.5 -#define TOTAL_MASS_NORMAL_ITEM 3.75 -#define TOTAL_MASS_BULKY_ITEM 5 -#define TOTAL_MASS_HUGE_ITEM 6.25 -#define TOTAL_MASS_GIGANTIC_ITEM 7.5 - -#define TOTAL_MASS_HAND_REPLACEMENT 5 //standard punching stamina cost. most hand replacements are huge items anyway. -#define TOTAL_MASS_MEDIEVAL_WEAPON 3.6 //very, very generic average sword/warpick/etc. weight in pounds. -#define TOTAL_MASS_TOY_SWORD 1.5 +/*ALL DEFINES RELATED TO COMBAT GO HERE*/ + +//Damage and status effect defines + +//Damage defines //TODO: merge these down to reduce on defines +#define BRUTE "brute" +#define BURN "fire" +#define TOX "tox" +#define OXY "oxy" +#define CLONE "clone" +#define STAMINA "stamina" +#define BRAIN "brain" + +//bitflag damage defines used for suicide_act +#define BRUTELOSS (1<<0) +#define FIRELOSS (1<<1) +#define TOXLOSS (1<<2) +#define OXYLOSS (1<<3) +#define SHAME (1<<4) +#define MANUAL_SUICIDE (1<<5) //suicide_act will do the actual killing. + +#define EFFECT_STUN "stun" +#define EFFECT_KNOCKDOWN "knockdown" +#define EFFECT_UNCONSCIOUS "unconscious" +#define EFFECT_IRRADIATE "irradiate" +#define EFFECT_STUTTER "stutter" +#define EFFECT_SLUR "slur" +#define EFFECT_EYE_BLUR "eye_blur" +#define EFFECT_DROWSY "drowsy" +#define EFFECT_JITTER "jitter" + +//Bitflags defining which status effects could be or are inflicted on a mob +#define CANSTUN (1<<0) +#define CANKNOCKDOWN (1<<1) +#define CANUNCONSCIOUS (1<<2) +#define CANPUSH (1<<3) +#define GODMODE (1<<4) + +//Health Defines +#define HEALTH_THRESHOLD_CRIT 0 +#define HEALTH_THRESHOLD_FULLCRIT -30 +#define HEALTH_THRESHOLD_DEAD -100 + +//Actual combat defines + +//click cooldowns, in tenths of a second, used for various combat actions +#define CLICK_CD_MELEE 8 +#define CLICK_CD_RANGE 4 +#define CLICK_CD_RAPID 2 +#define CLICK_CD_CLICK_ABILITY 6 +#define CLICK_CD_BREAKOUT 100 +#define CLICK_CD_HANDCUFFED 10 +#define CLICK_CD_RESIST 20 +#define CLICK_CD_GRABBING 10 + +//Cuff resist speeds +#define FAST_CUFFBREAK 1 +#define INSTANT_CUFFBREAK 2 + +//Grab levels +#define GRAB_PASSIVE 0 +#define GRAB_AGGRESSIVE 1 +#define GRAB_NECK 2 +#define GRAB_KILL 3 + +//slowdown when in softcrit +#define SOFTCRIT_ADD_SLOWDOWN 6 + +//Attack types for checking shields/hit reactions +#define MELEE_ATTACK 1 +#define UNARMED_ATTACK 2 +#define PROJECTILE_ATTACK 3 +#define THROWN_PROJECTILE_ATTACK 4 +#define LEAP_ATTACK 5 + +//attack visual effects +#define ATTACK_EFFECT_PUNCH "punch" +#define ATTACK_EFFECT_KICK "kick" +#define ATTACK_EFFECT_SMASH "smash" +#define ATTACK_EFFECT_CLAW "claw" +#define ATTACK_EFFECT_DISARM "disarm" +#define ATTACK_EFFECT_ASS_SLAP "ass_slap" +#define ATTACK_EFFECT_FACE_SLAP "face_slap" +#define ATTACK_EFFECT_BITE "bite" +#define ATTACK_EFFECT_MECHFIRE "mech_fire" +#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" +#define ATTACK_EFFECT_BOOP "boop" //Honk + +//intent defines +#define INTENT_HELP "help" +#define INTENT_GRAB "grab" +#define INTENT_DISARM "disarm" +#define INTENT_HARM "harm" +//NOTE: INTENT_HOTKEY_* defines are not actual intents! +//they are here to support hotkeys +#define INTENT_HOTKEY_LEFT "left" +#define INTENT_HOTKEY_RIGHT "right" + +//the define for visible message range in combat +#define COMBAT_MESSAGE_RANGE 3 +#define DEFAULT_MESSAGE_RANGE 7 + +//Shove knockdown lengths (deciseconds) +#define SHOVE_KNOCKDOWN_SOLID 30 +#define SHOVE_KNOCKDOWN_HUMAN 30 +#define SHOVE_KNOCKDOWN_TABLE 30 +#define SHOVE_KNOCKDOWN_COLLATERAL 10 +//Shove slowdown +#define SHOVE_SLOWDOWN_ID "shove_slowdown" +#define SHOVE_SLOWDOWN_LENGTH 30 +#define SHOVE_SLOWDOWN_STRENGTH 0.85 //multiplier +//Shove disarming item list +GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( + /obj/item/gun))) + + +//Combat object defines + +//Embedded objects +#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) +#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) +#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) +#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) +#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) +#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) +#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) +#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) +#define EMBEDDED_UNSAFE_REMOVAL_TIME 150 //A Time in ticks, total removal time = (this/item.w_class) + +//Gun weapon weight +#define WEAPON_LIGHT 1 +#define WEAPON_MEDIUM 2 +#define WEAPON_HEAVY 3 +//Gun trigger guards +#define TRIGGER_GUARD_ALLOW_ALL -1 +#define TRIGGER_GUARD_NONE 0 +#define TRIGGER_GUARD_NORMAL 1 +//E-gun self-recharge values +#define EGUN_NO_SELFCHARGE 0 +#define EGUN_SELFCHARGE 1 +#define EGUN_SELFCHARGE_BORG 2 + +//Object/Item sharpness +#define IS_BLUNT 0 +#define IS_SHARP 1 +#define IS_SHARP_ACCURATE 2 + +//His Grace. +#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. +#define HIS_GRACE_PECKISH 20 //Slightly hungry. +#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. +#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. +#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. +#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. +#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. + +#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. + +#define EXPLODE_NONE 0 //Don't even ask me why we need this. +#define EXPLODE_DEVASTATE 1 +#define EXPLODE_HEAVY 2 +#define EXPLODE_LIGHT 3 + +#define EMP_HEAVY 1 +#define EMP_LIGHT 2 + +#define GRENADE_CLUMSY_FUMBLE 1 +#define GRENADE_NONCLUMSY_FUMBLE 2 +#define GRENADE_NO_FUMBLE 3 + +#define BODY_ZONE_HEAD "head" +#define BODY_ZONE_CHEST "chest" +#define BODY_ZONE_L_ARM "l_arm" +#define BODY_ZONE_R_ARM "r_arm" +#define BODY_ZONE_L_LEG "l_leg" +#define BODY_ZONE_R_LEG "r_leg" + +#define BODY_ZONE_PRECISE_EYES "eyes" +#define BODY_ZONE_PRECISE_MOUTH "mouth" +#define BODY_ZONE_PRECISE_GROIN "groin" +#define BODY_ZONE_PRECISE_L_HAND "l_hand" +#define BODY_ZONE_PRECISE_R_HAND "r_hand" +#define BODY_ZONE_PRECISE_L_FOOT "l_foot" +#define BODY_ZONE_PRECISE_R_FOOT "r_foot" + +//We will round to this value in damage calculations. +#define DAMAGE_PRECISION 0.1 + +//bullet_act() return values +#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. +#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. + +//items total mass, used to calculate their attacks' stamina costs. If not defined, the cost will be (w_class * 1.25) +#define TOTAL_MASS_TINY_ITEM 1.25 +#define TOTAL_MASS_SMALL_ITEM 2.5 +#define TOTAL_MASS_NORMAL_ITEM 3.75 +#define TOTAL_MASS_BULKY_ITEM 5 +#define TOTAL_MASS_HUGE_ITEM 6.25 +#define TOTAL_MASS_GIGANTIC_ITEM 7.5 + +#define TOTAL_MASS_HAND_REPLACEMENT 5 //standard punching stamina cost. most hand replacements are huge items anyway. +#define TOTAL_MASS_MEDIEVAL_WEAPON 3.6 //very, very generic average sword/warpick/etc. weight in pounds. +#define TOTAL_MASS_TOY_SWORD 1.5 diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index e0b916cb..05d31154 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -81,3 +81,7 @@ #define MAX_NAME_LEN 42 #define MAX_BROADCAST_LEN 512 #define MAX_CHARTER_LEN 80 + +// Audio/Visual Flags. Used to determine what sense are required to notice a message. +#define MSG_VISUAL (1<<0) +#define MSG_AUDIBLE (1<<1) \ No newline at end of file diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 8a47af52..fb063f94 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -58,6 +58,10 @@ var/screen_start_y = 2 //End + var/limited_random_access = FALSE //Quick if statement in accessible_items to determine if we care at all about what people can access at once. + var/limited_random_access_stack_position = 0 //If >0, can only access top items + var/limited_random_access_stack_bottom_up = FALSE //If TRUE, above becomes bottom items + /datum/component/storage/Initialize(datum/component/storage/concrete/master) if(!isatom(parent)) return COMPONENT_INCOMPATIBLE @@ -626,9 +630,9 @@ if(M == viewing) to_chat(usr, "You put [I] [insert_preposition]to [parent].") else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) /datum/component/storage/proc/update_icon() if(isobj(parent)) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 3573b4a4..7e2a6bcf 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -1042,7 +1042,7 @@ GLOBAL_LIST_EMPTY(PDAs) if (ismob(loc)) var/mob/M = loc - M.show_message("Your [src] explodes!", 1) + M.show_message("Your [src] explodes!", MSG_VISUAL, "You hear a loud *pop*!", MSG_AUDIBLE) else visible_message("[src] explodes!", "You hear a loud *pop*!") diff --git a/code/game/objects/items/devices/PDA/virus_cart.dm b/code/game/objects/items/devices/PDA/virus_cart.dm index 28bc559b..d9039946 100644 --- a/code/game/objects/items/devices/PDA/virus_cart.dm +++ b/code/game/objects/items/devices/PDA/virus_cart.dm @@ -72,12 +72,12 @@ difficulty += 2 var/datum/component/uplink/hidden_uplink = target.GetComponent(/datum/component/uplink) if(!target.detonatable || prob(difficulty * 15) || (hidden_uplink)) - U.show_message("An error flashes on your [src].", 1) + U.show_message("An error flashes on your [src].", MSG_VISUAL) else message_admins("[!is_special_character(U) ? "Non-antag " : ""][ADMIN_LOOKUPFLW(U)] triggered a PDA explosion on [target.name] at [ADMIN_VERBOSEJMP(target)].") var/message_log = "triggered a PDA explosion on [target.name] at [AREACOORD(target)]." U.log_message(message_log, LOG_ATTACK) - U.show_message("Success!", 1) + U.show_message("Success!", MSG_VISUAL) target.explode() else to_chat(U, "PDA not found.") diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index f178524c..48ecf827 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -1,36 +1,36 @@ -/obj/item/grenade/flashbang - name = "flashbang" - icon_state = "flashbang" - item_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - var/flashbang_range = 7 //how many tiles away the mob will be stunned. - -/obj/item/grenade/flashbang/prime() - update_mob() - var/flashbang_turf = get_turf(src) - if(!flashbang_turf) - return - do_sparks(rand(5, 9), FALSE, src) - playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9) - new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2) - for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) - bang(get_turf(M), M) - qdel(src) - -/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) - if(M.stat == DEAD) //They're dead! - return - M.show_message("BANG", 2) - var/distance = max(0,get_dist(get_turf(src),T)) - -//Flash - if(M.flash_act(affect_silicon = 1)) - M.Knockdown(max(200/max(1,distance), 60)) -//Bang - if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. - M.Knockdown(200) - M.soundbang_act(1, 200, 10, 15) - - else - M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) +/obj/item/grenade/flashbang + name = "flashbang" + icon_state = "flashbang" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/flashbang_range = 7 //how many tiles away the mob will be stunned. + +/obj/item/grenade/flashbang/prime() + update_mob() + var/flashbang_turf = get_turf(src) + if(!flashbang_turf) + return + do_sparks(rand(5, 9), FALSE, src) + playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9) + new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2) + for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) + bang(get_turf(M), M) + qdel(src) + +/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) + if(M.stat == DEAD) //They're dead! + return + M.show_message("BANG", MSG_AUDIBLE) + var/distance = max(0,get_dist(get_turf(src),T)) + +//Flash + if(M.flash_act(affect_silicon = 1)) + M.Knockdown(max(200/max(1,distance), 60)) +//Bang + if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. + M.Knockdown(200) + M.soundbang_act(1, 200, 10, 15) + + else + M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm index 36043328..c50b0fae 100644 --- a/code/game/objects/items/plushes.dm +++ b/code/game/objects/items/plushes.dm @@ -158,7 +158,7 @@ //we are not catholic if(young == TRUE || Kisser.young == TRUE) - user.show_message("[src] plays tag with [Kisser].", 1, + user.show_message("[src] plays tag with [Kisser].", MSG_VISUAL, "They're happy.", 0) Kisser.cheer_up() cheer_up() @@ -166,10 +166,10 @@ //never again else if(Kisser in scorned) //message, visible, alternate message, neither visible nor audible - user.show_message("[src] rejects the advances of [Kisser]!", 1, + user.show_message("[src] rejects the advances of [Kisser]!", MSG_VISUAL, "That didn't feel like it worked.", 0) else if(src in Kisser.scorned) - user.show_message("[Kisser] realises who [src] is and turns away.", 1, + user.show_message("[Kisser] realises who [src] is and turns away.", MSG_VISUAL, "That didn't feel like it worked.", 0) //first comes love @@ -190,7 +190,7 @@ new_lover(Kisser) Kisser.new_lover(src) else - user.show_message("[src] rejects the advances of [Kisser], maybe next time?", 1, + user.show_message("[src] rejects the advances of [Kisser], maybe next time?", MSG_VISUAL, "That didn't feel like it worked, this time.", 0) //then comes marriage diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 70029d00..4a7b16c4 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -1,1358 +1,1358 @@ -/* - * 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, - * Ammo types, - * Action Figure Boxes, - * Various paper bags, - * Colored 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" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - resistance_flags = FLAMMABLE - var/foldable = /obj/item/stack/sheet/cardboard - var/illustration = "writing" - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //exploits ahoy - -/obj/item/storage/box/Initialize(mapload) - . = ..() - update_icon() - -/obj/item/storage/box/suicide_act(mob/living/carbon/user) - var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) - if(myhead) - user.visible_message("[user] puts [user.p_their()] head into \the [src], and begins closing it! It looks like [user.p_theyre()] trying to commit suicide!") - myhead.dismember() - myhead.forceMove(src)//force your enemies to kill themselves with your head collection box! - playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) - return BRUTELOSS - user.visible_message("[user] beating [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/box/update_icon() - . = ..() - if(illustration) - cut_overlays() - add_overlay(illustration) - -/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 - - to_chat(user, "You fold [src] flat.") - var/obj/item/I = new foldable - qdel(src) - user.put_in_hands(I) - -/obj/item/storage/box/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stack/packageWrap)) - return 0 - return ..() - -//Disk boxes -/obj/item/storage/box/disks - name = "diskette box" - illustration = "disk_kit" - -/obj/item/storage/box/disks/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/data(src) - -/obj/item/storage/box/disks_plantgene - name = "plant data disks box" - illustration = "disk_kit" - -/obj/item/storage/box/disks_plantgene/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/plantgene(src) - -/obj/item/storage/box/disks_nanite - name = "nanite program disks box" - illustration = "disk_kit" - -/obj/item/storage/box/disks_nanite/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/nanite_program(src) - -// Ordinary survival box -/obj/item/storage/box/survival/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/survival/radio/PopulateContents() - ..() // we want the survival stuff too. - new /obj/item/radio/off(src) - -/obj/item/storage/box/survival_mining/PopulateContents() - new /obj/item/clothing/mask/gas/explorer(src) - new /obj/item/crowbar/red(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -// Engineer survival box -/obj/item/storage/box/engineer/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/engineer/radio/PopulateContents() - ..() // we want the regular items too. - new /obj/item/radio/off(src) - -// Syndie survival box -/obj/item/storage/box/syndie/PopulateContents() - new /obj/item/clothing/mask/gas/syndicate(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -// Security survival box -/obj/item/storage/box/security/PopulateContents() - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/security/radio/PopulateContents() - ..() // we want the regular stuff too - new /obj/item/radio/off(src) - -/obj/item/storage/box/seclooking - icon_state = "secbox" - illustration = null - -/obj/item/storage/box/cells - name = "box of powercells" - desc = "Contains powercells." - illustration = "power_cell" - -/obj/item/storage/box/ammoshells - name = "box of loose ammo" - desc = "Contains loose ammo." - illustration = "loose_ammo" - -/obj/item/storage/box/otwo - name = "box of o2 supplies" - desc = "Contains o2 supplies." - illustration = "02" - -/obj/item/storage/box/otwo/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - -/obj/item/storage/box/gloves - name = "box of latex gloves" - desc = "Contains sterile latex gloves." - illustration = "latex" - -/obj/item/storage/box/gloves/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/gloves/color/latex(src) - -/obj/item/storage/box/masks - name = "box of sterile masks" - desc = "This box contains sterile medical masks." - illustration = "sterile" - -/obj/item/storage/box/masks/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/mask/surgical(src) - -/obj/item/storage/box/syringes - name = "box of syringes" - desc = "A box full of syringes." - illustration = "syringe" - -/obj/item/storage/box/syringes/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/syringe(src) - -/obj/item/storage/box/medipens - name = "box of medipens" - desc = "A box full of epinephrine MediPens." - illustration = "syringe" - -/obj/item/storage/box/medipens/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/hypospray/medipen(src) - -/obj/item/storage/box/medipens/utility - name = "stimpack value kit" - desc = "A box with several stimpack medipens for the economical miner." - illustration = "syringe" - -/obj/item/storage/box/medipens/utility/PopulateContents() - ..() // includes regular medipens. - for(var/i in 1 to 5) - new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) - -/obj/item/storage/box/beakers - name = "box of beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker( src ) - -/obj/item/storage/box/beakers/bluespace - name = "box of bluespace beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/bluespace/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/medsprays - name = "box of medical sprayers" - desc = "A box full of medical sprayers, with unscrewable caps and precision spray heads." - -/obj/item/storage/box/medsprays/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/medspray( src ) - -/obj/item/storage/box/injectors - name = "box of DNA injectors" - desc = "This box contains injectors, it seems." - -/obj/item/storage/box/injectors/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/dnainjector/h2m(src) - for(var/i in 1 to 3) - new /obj/item/dnainjector/m2h(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 = "secbox" - illustration = "flashbang" - -/obj/item/storage/box/flashbangs/PopulateContents() - for(var/i in 1 to 7) - 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 = "secbox" - illustration = "flashbang" - -/obj/item/storage/box/flashes/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/assembly/flash/handheld(src) - -/obj/item/storage/box/wall_flash - name = "wall-mounted flash kit" - desc = "This box contains everything necessary to build a wall-mounted flash. WARNING: Flashes can cause serious eye damage, protective eyewear is required." - illustration = "flashbang" - -/obj/item/storage/box/wall_flash/PopulateContents() - var/id = rand(1000, 9999) - // FIXME what if this conflicts with an existing one? - - new /obj/item/wallframe/button(src) - new /obj/item/electronics/airlock(src) - var/obj/item/assembly/control/flasher/remote = new(src) - remote.id = id - var/obj/item/wallframe/flasher/frame = new(src) - frame.id = id - new /obj/item/assembly/flash/handheld(src) - new /obj/item/screwdriver(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." - illustration = "flashbang" - -/obj/item/storage/box/teargas/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/chem_grenade/teargas(src) - -/obj/item/storage/box/emps - name = "box of emp grenades" - desc = "A box with 5 emp grenades." - illustration = "flashbang" - -/obj/item/storage/box/emps/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/grenade/empgrenade(src) - -/obj/item/storage/box/trackimp - name = "boxed tracking implant kit" - desc = "Box full of scum-bag tracking utensils." - illustration = "implant" - -/obj/item/storage/box/trackimp/PopulateContents() - for(var/i in 1 to 4) - 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." - illustration = "implant" - -/obj/item/storage/box/minertracker/PopulateContents() - for(var/i in 1 to 3) - 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 = "boxed chemical implant kit" - desc = "Box of stuff used to implant chemicals." - illustration = "implant" - -/obj/item/storage/box/chemimp/PopulateContents() - for(var/i in 1 to 5) - 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." - illustration = "implant" - -/obj/item/storage/box/exileimp/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/implantcase/exile(src) - new /obj/item/implanter(src) - -/obj/item/storage/box/bodybags - name = "body bags" - desc = "The label indicates that it contains body bags." - illustration = "bodybags" - -/obj/item/storage/box/bodybags/PopulateContents() - ..() - for(var/i in 1 to 7) - new /obj/item/bodybag(src) - -/obj/item/storage/box/rxglasses - name = "box of prescription glasses" - desc = "This box contains nerd glasses." - illustration = "glasses" - -/obj/item/storage/box/rxglasses/PopulateContents() - for(var/i in 1 to 7) - 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." - -/obj/item/storage/box/drinkingglasses/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - -/obj/item/storage/box/condimentbottles - name = "box of condiment bottles" - desc = "It has a large ketchup smear on it." - -/obj/item/storage/box/condimentbottles/PopulateContents() - for(var/i in 1 to 6) - 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." - -/obj/item/storage/box/cups/PopulateContents() - for(var/i in 1 to 7) - 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 = "donkpocketbox" - illustration=null - -/obj/item/storage/box/donkpockets/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket)) - -/obj/item/storage/box/donkpockets/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - -/obj/item/storage/box/donkpockets/donkpocketspicy - name = "box of spicy-flavoured donk-pockets" - icon_state = "donkpocketboxspicy" - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/spicy - -/obj/item/storage/box/donkpockets/donkpocketspicy/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/spicy)) - -/obj/item/storage/box/donkpockets/donkpocketspicy/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket/spicy(src) - -/obj/item/storage/box/donkpockets/donkpocketteriyaki - name = "box of teriyaki-flavoured donk-pockets" - icon_state = "donkpocketboxteriyaki" - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki - -/obj/item/storage/box/donkpockets/donkpocketteriyaki/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/teriyaki)) - -/obj/item/storage/box/donkpockets/donkpocketteriyaki/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki(src) - -/obj/item/storage/box/donkpockets/donkpocketpizza - name = "box of pizza-flavoured donk-pockets" - icon_state = "donkpocketboxpizza" - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/pizza - -/obj/item/storage/box/donkpockets/donkpocketpizza/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/pizza)) - -/obj/item/storage/box/donkpockets/donkpocketpizza/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket/pizza(src) - -/obj/item/storage/box/donkpockets/donkpocketgondola - name = "box of gondola-flavoured donk-pockets" - icon_state = "donkpocketboxgondola" - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/gondola - -/obj/item/storage/box/donkpockets/donkpocketgondola/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/gondola)) - -/obj/item/storage/box/donkpockets/donkpocketgondola/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket/gondola(src) - -/obj/item/storage/box/donkpockets/donkpocketberry - name = "box of berry-flavoured donk-pockets" - icon_state = "donkpocketboxberry" - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/berry - -/obj/item/storage/box/donkpockets/donkpocketberry/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/berry)) - -/obj/item/storage/box/donkpockets/donkpocketberry/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket/berry(src) - -/obj/item/storage/box/donkpockets/donkpockethonk - name = "box of banana-flavoured donk-pockets" - icon_state = "donkpocketboxbanana" - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/honk - -/obj/item/storage/box/donkpockets/donkpockethonk/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/honk)) - -/obj/item/storage/box/donkpockets/donkpockethonk/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket/honk(src) - -/obj/item/storage/box/monkeycubes - name = "monkey cube box" - desc = "Drymate brand monkey cubes. Just add water!" - icon_state = "monkeycubebox" - illustration = null - -/obj/item/storage/box/monkeycubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 7 - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/monkeycubes/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/food/snacks/monkeycube(src) - -/obj/item/storage/box/ids - name = "box of spare IDs" - desc = "Has so many empty IDs." - illustration = "id" - -/obj/item/storage/box/ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id(src) - -//Some spare PDAs in a box -/obj/item/storage/box/PDAs - name = "spare PDAs" - desc = "A box of spare PDA microcomputers." - illustration = "pda" - -/obj/item/storage/box/PDAs/PopulateContents() - new /obj/item/pda(src) - new /obj/item/pda(src) - new /obj/item/pda(src) - new /obj/item/pda(src) - new /obj/item/cartridge/head(src) - - var/newcart = pick( /obj/item/cartridge/engineering, - /obj/item/cartridge/security, - /obj/item/cartridge/medical, - /obj/item/cartridge/signal/toxins, - /obj/item/cartridge/quartermaster) - new newcart(src) - -/obj/item/storage/box/silver_ids - name = "box of spare silver IDs" - desc = "Shiny IDs for important people." - illustration = "id" - -/obj/item/storage/box/silver_ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id/silver(src) - -/obj/item/storage/box/prisoner - name = "box of prisoner IDs" - desc = "Take away their last shred of dignity, their name." - illustration = "id" - -/obj/item/storage/box/prisoner/PopulateContents() - ..() - 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 = "box of PDA security cartridges" - desc = "A box full of PDA cartridges used by Security." - illustration = "pda" - -/obj/item/storage/box/seccarts/PopulateContents() - new /obj/item/cartridge/detective(src) - for(var/i in 1 to 6) - new /obj/item/cartridge/security(src) - -/obj/item/storage/box/firingpins - name = "box of standard firing pins" - desc = "A box full of standard firing pins, to allow newly-developed firearms to operate." - illustration = "firing_pins" - -/obj/item/storage/box/firingpins/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/firing_pin(src) - -/obj/item/storage/box/lasertagpins - name = "box of laser tag firing pins" - desc = "A box full of laser tag firing pins, to allow newly-developed firearms to require wearing brightly coloured plastic armor before being able to be used." - illustration = "firing_pins" - -/obj/item/storage/box/lasertagpins/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/firing_pin/tag/red(src) - new /obj/item/firing_pin/tag/blue(src) - -/obj/item/storage/box/handcuffs - name = "box of spare handcuffs" - desc = "A box full of handcuffs." - icon_state = "secbox" - illustration = "handcuff" - -/obj/item/storage/box/handcuffs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs(src) - -/obj/item/storage/box/zipties - name = "box of spare zipties" - desc = "A box full of zipties." - icon_state = "secbox" - illustration = "handcuff" - -/obj/item/storage/box/zipties/PopulateContents() - for(var/i in 1 to 7) - 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 = "alienbox" - illustration = "handcuff" - -/obj/item/storage/box/alienhandcuffs/PopulateContents() - 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 = "syndiebox" - -/obj/item/storage/box/fakesyndiesuit/PopulateContents() - new /obj/item/clothing/head/syndicatefake(src) - new /obj/item/clothing/suit/syndicatefake(src) - -/obj/item/storage/box/mousetraps - name = "box of Pest-B-Gon mousetraps" - desc = "Keep out of reach of children." - illustration = "mousetraps" - -/obj/item/storage/box/mousetraps/PopulateContents() - for(var/i in 1 to 6) - 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." - illustration = "pillbox" - -/obj/item/storage/box/pillbottles/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/storage/pill_bottle(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" - -/obj/item/storage/box/snappops/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/toy/snappop)) - STR.max_items = 8 - -/obj/item/storage/box/snappops/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) - -/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" - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BELT - -/obj/item/storage/box/matches/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.can_hold = typecacheof(list(/obj/item/match)) - -/obj/item/storage/box/matches/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) - -/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/match)) - W.matchignite() - -/obj/item/storage/box/lights - name = "box of replacement bulbs" - icon = 'icons/obj/storage.dmi' - illustration = "light" - desc = "This box is shaped on the inside so that only light tubes and bulbs fit." - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - foldable = /obj/item/stack/sheet/cardboard //BubbleWrap - -/obj/item/storage/box/lights/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 21 - STR.can_hold = typecacheof(list(/obj/item/light/tube, /obj/item/light/bulb)) - STR.max_combined_w_class = 21 - STR.click_gather = TRUE - -/obj/item/storage/box/lights/bulbs/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/bulb(src) - -/obj/item/storage/box/lights/tubes - name = "box of replacement tubes" - illustration = "lighttube" - -/obj/item/storage/box/lights/tubes/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/tube(src) - -/obj/item/storage/box/lights/mixed - name = "box of replacement lights" - illustration = "lightmixed" - -/obj/item/storage/box/lights/mixed/PopulateContents() - for(var/i in 1 to 14) - new /obj/item/light/tube(src) - for(var/i in 1 to 7) - new /obj/item/light/bulb(src) - -/obj/item/storage/box/deputy - name = "box of deputy armbands" - desc = "To be issued to those authorized to act as deputy of security." - -/obj/item/storage/box/deputy/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/accessory/armband/deputy(src) - -/obj/item/storage/box/metalfoam - name = "box of metal foam grenades" - desc = "To be used to rapidly seal hull breaches." - illustration = "flashbang" - -/obj/item/storage/box/metalfoam/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/chem_grenade/metalfoam(src) - -/obj/item/storage/box/smart_metal_foam - name = "box of smart metal foam grenades" - desc = "Used to rapidly seal hull breaches. This variety conforms to the walls of its area." - illustration = "flashbang" - -/obj/item/storage/box/smart_metal_foam/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/chem_grenade/smart_metal_foam(src) - -/obj/item/storage/box/hug - name = "box of hugs" - desc = "A special box for sensitive people." - icon_state = "hugbox" - illustration = "heart" - 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].") - -/////clown box & honkbot assembly -/obj/item/storage/box/clown - name = "clown box" - desc = "A colorful cardboard box for the clown" - illustration = "clown" - -/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) - if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) - if(contents.len) //prevent accidently deleting contents - to_chat(user, "You need to empty [src] out first!") - return - if(!user.temporarilyRemoveItemFromInventory(I)) - return - qdel(I) - to_chat(user, "You add some wheels to the [src]! You've got an honkbot assembly now! Honk!") - var/obj/item/bot_assembly/honkbot/A = new - qdel(src) - user.put_in_hands(A) - else - return ..() - -////// -/obj/item/storage/box/hug/medical/PopulateContents() - new /obj/item/stack/medical/bruise_pack(src) - new /obj/item/stack/medical/ointment(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - -// Clown survival box -/obj/item/storage/box/hug/survival/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/rubbershot - name = "box of rubber shots" - desc = "A box full of rubber shots, designed for riot shotguns." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/rubbershot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/rubbershot(src) - -/obj/item/storage/box/lethalshot - name = "box of buckshot (Lethal)" - desc = "A box full of lethal shots, designed for riot shotguns." - icon_state = "lethalshot_box" - illustration = null - -/obj/item/storage/box/lethalshot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/buckshot(src) - -/obj/item/storage/box/beanbag - name = "box of beanbags" - desc = "A box full of beanbag shells." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/beanbag/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/ammo_casing/shotgun/beanbag(src) - -/obj/item/storage/box/lethalslugs - name = "box of 12g shotgun slugs" - desc = "A box full of lethal 12g slug, designed for riot shotguns." - icon_state = "12g_box" - illustration = null - -/obj/item/storage/box/lethalslugs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun(src) - -/obj/item/storage/box/stunslug - name = "box of stun slugs" - desc = "A box full of stun 12g slugs." - icon_state = "stunslug_box" - illustration = null - -/obj/item/storage/box/stunslug/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/stunslug(src) - -/obj/item/storage/box/techsslug - name = "box of tech shotgun shells" - desc = "A box full of tech shotgun shells." - icon_state = "techslug_box" - illustration = null - -/obj/item/storage/box/techsslug/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/techshell(src) - -/obj/item/storage/box/fireshot - name = "box of incendiary ammo" - desc = "A box full of incendiary ammo." - icon_state = "fireshot_box" - illustration = null - -/obj/item/storage/box/fireshot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/incendiary(src) - -/obj/item/storage/box/actionfigure - name = "box of action figures" - desc = "The latest set of collectable action figures." - icon_state = "box" - -/obj/item/storage/box/actionfigure/PopulateContents() - for(var/i in 1 to 4) - var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) - new randomFigure(src) - -/obj/item/storage/box/mechfigures - name = "box of mech figures" - desc = "The latest set of collectable mech figures." - icon_state = "box" - -/obj/item/storage/box/mechfigures/PopulateContents() - for(var/i in 1 to 4) - var/randomFigure = pick(subtypesof(/obj/item/toy/prize/)) - new randomFigure(src) - - - - -/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 - /// A list of all available papersack reskins - var/list/papersack_designs = list() - -/obj/item/storage/box/papersack/Initialize(mapload) - . = ..() - papersack_designs = sortList(list( - "None" = image(icon = src.icon, icon_state = "paperbag_None"), - "NanotrasenStandard" = image(icon = src.icon, icon_state = "paperbag_NanotrasenStandard"), - "SyndiSnacks" = image(icon = src.icon, icon_state = "paperbag_SyndiSnacks"), - "Heart" = image(icon = src.icon, icon_state = "paperbag_Heart"), - "SmileyFace" = image(icon = src.icon, icon_state = "paperbag_SmileyFace") - )) - -/obj/item/storage/box/papersack/update_icon_state() - if(contents.len == 0) - 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)) - var/choice = show_radial_menu(user, src , papersack_designs, custom_check = CALLBACK(src, .proc/check_menu, user, W), radius = 36, require_near = TRUE) - if(!choice) - return FALSE - if(icon_state == "paperbag_[choice]") - return FALSE - switch(choice) - if("None") - desc = "A sack neatly crafted out of paper." - if("NanotrasenStandard") - desc = "A standard Kinaris paper lunch sack for loyal employees on the go." - if("SyndiSnacks") - 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("SmileyFace") - desc = "A paper sack with a crude smile etched onto the side." - else - return FALSE - to_chat(user, "You make some modifications to [src] using your pen.") - icon_state = "paperbag_[choice]" - item_state = "paperbag_[choice]" - return FALSE - else if(W.is_sharp()) - if(!contents.len) - if(item_state == "paperbag_None") - user.show_message("You cut eyeholes into [src].", 1) - new /obj/item/clothing/head/papersack(user.loc) - qdel(src) - return FALSE - else if(item_state == "paperbag_SmileyFace") - user.show_message("You cut eyeholes into [src] and modify the design.", 1) - new /obj/item/clothing/head/papersack/smiley(user.loc) - qdel(src) - return FALSE - return ..() - -/** - * check_menu: Checks if we are allowed to interact with a radial menu - * - * Arguments: - * * user The mob interacting with a menu - * * P The pen used to interact with a menu - */ -/obj/item/storage/box/papersack/proc/check_menu(mob/user, obj/item/pen/P) - if(!istype(user)) - return FALSE - if(user.incapacitated()) - return FALSE - if(contents.len) - to_chat(user, "You can't modify [src] with items still inside!") - return FALSE - if(!P || !user.is_holding(P)) - to_chat(user, "You need a pen to modify [src]!") - return FALSE - return TRUE - -/obj/item/storage/box/ingredients //This box is for the randomly chosen version the chef spawns with, it shouldn't actually exist. - name = "ingredients box" - illustration = "fruit" - var/theme_name - -/obj/item/storage/box/ingredients/Initialize() - . = ..() - if(theme_name) - name = "[name] ([theme_name])" - desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." - item_state = "syringe_kit" - -/obj/item/storage/box/ingredients/wildcard - theme_name = "wildcard" - -/obj/item/storage/box/ingredients/wildcard/PopulateContents() - for(var/i in 1 to 7) - var/randomFood = pick(/obj/item/reagent_containers/food/snacks/grown/chili, - /obj/item/reagent_containers/food/snacks/grown/tomato, - /obj/item/reagent_containers/food/snacks/grown/carrot, - /obj/item/reagent_containers/food/snacks/grown/potato, - /obj/item/reagent_containers/food/snacks/grown/potato/sweet, - /obj/item/reagent_containers/food/snacks/grown/apple, - /obj/item/reagent_containers/food/snacks/chocolatebar, - /obj/item/reagent_containers/food/snacks/grown/cherries, - /obj/item/reagent_containers/food/snacks/grown/banana, - /obj/item/reagent_containers/food/snacks/grown/cabbage, - /obj/item/reagent_containers/food/snacks/grown/soybeans, - /obj/item/reagent_containers/food/snacks/grown/corn, - /obj/item/reagent_containers/food/snacks/grown/mushroom/plumphelmet, - /obj/item/reagent_containers/food/snacks/grown/mushroom/chanterelle) - new randomFood(src) - -/obj/item/storage/box/ingredients/fiesta - theme_name = "fiesta" - -/obj/item/storage/box/ingredients/fiesta/PopulateContents() - new /obj/item/reagent_containers/food/snacks/tortilla(src) - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - -/obj/item/storage/box/ingredients/italian - theme_name = "italian" - -/obj/item/storage/box/ingredients/italian/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - new /obj/item/reagent_containers/food/drinks/bottle/wine(src) - -/obj/item/storage/box/ingredients/vegetarian - theme_name = "vegetarian" - -/obj/item/storage/box/ingredients/vegetarian/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/carrot(src) - new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - -/obj/item/storage/box/ingredients/american - theme_name = "american" - -/obj/item/storage/box/ingredients/american/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - -/obj/item/storage/box/ingredients/fruity - theme_name = "fruity" - -/obj/item/storage/box/ingredients/fruity/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) - new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) - -/obj/item/storage/box/ingredients/sweets - theme_name = "sweets" - -/obj/item/storage/box/ingredients/sweets/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/cherries(src) - new /obj/item/reagent_containers/food/snacks/grown/banana(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - -/obj/item/storage/box/ingredients/delights - theme_name = "delights" - -/obj/item/storage/box/ingredients/delights/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) - new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) - new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/berries(src) - -/obj/item/storage/box/ingredients/grains - theme_name = "grains" - -/obj/item/storage/box/ingredients/grains/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/grown/oat(src) - new /obj/item/reagent_containers/food/snacks/grown/wheat(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/honeycomb(src) - new /obj/item/seeds/poppy(src) - -/obj/item/storage/box/ingredients/carnivore - theme_name = "carnivore" - -/obj/item/storage/box/ingredients/carnivore/PopulateContents() - new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) - new /obj/item/reagent_containers/food/snacks/spidereggs(src) - new /obj/item/reagent_containers/food/snacks/carpmeat(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - -/obj/item/storage/box/ingredients/exotic - theme_name = "exotic" - -/obj/item/storage/box/ingredients/exotic/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/carpmeat(src) - new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) - new /obj/item/reagent_containers/food/snacks/grown/cabbage(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - -/obj/item/storage/box/emptysandbags - name = "box of empty sandbags" - -/obj/item/storage/box/emptysandbags/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/emptysandbag(src) - -/obj/item/storage/box/rndboards - name = "\proper the liberator's legacy" - desc = "A box containing a gift for worthy golems." - -/obj/item/storage/box/rndboards/PopulateContents() - new /obj/item/circuitboard/machine/protolathe(src) - new /obj/item/circuitboard/machine/destructive_analyzer(src) - new /obj/item/circuitboard/machine/circuit_imprinter(src) - new /obj/item/circuitboard/computer/rdconsole(src) - -/obj/item/storage/box/silver_sulf - name = "box of silver sulfadiazine patches" - desc = "Contains patches used to treat burns." - -/obj/item/storage/box/silver_sulf/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - -/obj/item/storage/box/fountainpens - name = "box of fountain pens" - -/obj/item/storage/box/fountainpens/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/pen/fountain(src) - -/obj/item/storage/box/holy_grenades - name = "box of holy hand grenades" - desc = "Contains several grenades used to rapidly purge heresy." - illustration = "flashbang" - -/obj/item/storage/box/holy_grenades/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/chem_grenade/holy(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/PopulateContents() - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/manipulator(src) - new /obj/item/stock_parts/manipulator(src) - new /obj/item/stock_parts/manipulator(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/matter_bin(src) - new /obj/item/stock_parts/matter_bin(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." - icon_state = "syndiebox" - -/obj/item/storage/box/stockparts/deluxe/PopulateContents() - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/manipulator/femto(src) - new /obj/item/stock_parts/manipulator/femto(src) - new /obj/item/stock_parts/manipulator/femto(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - -//Colored boxes. -/obj/item/storage/box/green - icon_state = "box_green" - illustration = null - -/obj/item/storage/box/blue - icon_state = "box_blue" - illustration = null - -/obj/item/storage/box/purple - icon_state = "box_purple" - illustration = null - -/obj/item/storage/box/red - icon_state = "box_red" - illustration = null - -/obj/item/storage/box/yellow - icon_state = "box_yellow" - illustration = null - -/obj/item/storage/box/brown - icon_state = "box_brown" - illustration = null - -/obj/item/storage/box/pink - icon_state = "box_pink" - illustration = null - -/obj/item/storage/box/mre //base MRE type. - name = "Kinaris MRE Ration Kit Menu 0" - desc = "A package containing food suspended in an outdated bluespace pocket which lasts for centuries. If you're lucky you may even be able to enjoy the meal without getting food poisoning." - icon_state = "mre" - illustration = null - var/can_expire = TRUE - var/spawner_chance = 2 - var/expiration_date - var/expiration_date_min = 2300 - var/expiration_date_max = 2700 - -/obj/item/storage/box/mre/Initialize() - . = ..() - if(can_expire) - expiration_date = rand(expiration_date_min, expiration_date_max) - desc += "\nAn expiry date is listed on it. It reads: [expiration_date]" - var/spess_current_year = GLOB.year_integer + 540 - if(expiration_date < spess_current_year) - var/gross_risk = min(round(spess_current_year - expiration_date * 0.1), 1) - var/toxic_risk = min(round(spess_current_year - expiration_date * 0.01), 1) - for(var/obj/item/reagent_containers/food/snacks/S in contents) - if(prob(gross_risk)) - ENABLE_BITFIELD(S.foodtype, GROSS) - if(prob(toxic_risk)) - ENABLE_BITFIELD(S.foodtype, TOXIC) - -/obj/item/storage/box/mre/menu1 - name = "\improper Kinaris MRE Ration Kit Menu 1" - -/obj/item/storage/box/mre/menu1/safe - desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." - spawner_chance = 0 - can_expire = FALSE - -/obj/item/storage/box/mre/menu1/PopulateContents() - new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) - new /obj/item/reagent_containers/food/snacks/breadslice/creamcheese(src) - new /obj/item/reagent_containers/food/condiment/pack/ketchup(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -/obj/item/storage/box/mre/menu2 - name = "\improper Kinaris MRE Ration Kit Menu 2" - -/obj/item/storage/box/mre/menu2/safe - spawner_chance = 0 - desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." - can_expire = FALSE - -/obj/item/storage/box/mre/menu2/PopulateContents() - new /obj/item/reagent_containers/food/snacks/omelette(src) - new /obj/item/reagent_containers/food/snacks/meat/cutlet/plain(src) - new /obj/item/reagent_containers/food/snacks/fries(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -/obj/item/storage/box/mre/menu3 - name = "\improper Kinaris MRE Ration Kit Menu 3" - desc = "The holy grail of MREs. This item contains the fabled MRE pizza, spicy nachos and a sample of coffee instant type 2. Any NT employee lucky enough to get their hands on one of these is truly blessed." - icon_state = "menu3" - can_expire = FALSE //always fresh, never expired. - spawner_chance = 1 - -/obj/item/storage/box/mre/menu3/PopulateContents() - new /obj/item/reagent_containers/food/snacks/pizzaslice/pepperoni(src) - new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) - new /obj/item/reagent_containers/food/snacks/cubannachos(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - new /obj/item/reagent_containers/food/drinks/coffee/type2(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -/obj/item/storage/box/mre/menu4 - name = "\improper Kinaris MRE Ration Kit Menu 4" - -/obj/item/storage/box/mre/menu4/safe - spawner_chance = 0 - desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." - can_expire = FALSE - -/obj/item/storage/box/mre/menu4/PopulateContents() - if(prob(66)) - new /obj/item/reagent_containers/food/snacks/salad/boiledrice(src) - else - new /obj/item/reagent_containers/food/snacks/salad/ricebowl(src) - new /obj/item/reagent_containers/food/snacks/burger/tofu(src) - new /obj/item/reagent_containers/food/snacks/salad/fruit(src) - new /obj/item/reagent_containers/food/snacks/cracker(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -//Where do I put this? -/obj/item/secbat - name = "Secbat box" - desc = "Contained inside is a secbat for use with law enforcement." - icon = 'icons/obj/storage.dmi' - icon_state = "box" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - -/obj/item/secbat/attack_self(mob/user) - new /mob/living/simple_animal/hostile/retaliate/bat/secbat(user.loc) - to_chat(user, "You open the box, releasing the secbat!") - var/obj/item/stack/sheet/cardboard/I = new(user.drop_location()) - qdel(src) - user.put_in_hands(I) +/* + * 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, + * Ammo types, + * Action Figure Boxes, + * Various paper bags, + * Colored 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" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + resistance_flags = FLAMMABLE + var/foldable = /obj/item/stack/sheet/cardboard + var/illustration = "writing" + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //exploits ahoy + +/obj/item/storage/box/Initialize(mapload) + . = ..() + update_icon() + +/obj/item/storage/box/suicide_act(mob/living/carbon/user) + var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) + if(myhead) + user.visible_message("[user] puts [user.p_their()] head into \the [src], and begins closing it! It looks like [user.p_theyre()] trying to commit suicide!") + myhead.dismember() + myhead.forceMove(src)//force your enemies to kill themselves with your head collection box! + playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) + return BRUTELOSS + user.visible_message("[user] beating [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/box/update_icon() + . = ..() + if(illustration) + cut_overlays() + add_overlay(illustration) + +/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 + + to_chat(user, "You fold [src] flat.") + var/obj/item/I = new foldable + qdel(src) + user.put_in_hands(I) + +/obj/item/storage/box/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stack/packageWrap)) + return 0 + return ..() + +//Disk boxes +/obj/item/storage/box/disks + name = "diskette box" + illustration = "disk_kit" + +/obj/item/storage/box/disks/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/data(src) + +/obj/item/storage/box/disks_plantgene + name = "plant data disks box" + illustration = "disk_kit" + +/obj/item/storage/box/disks_plantgene/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/plantgene(src) + +/obj/item/storage/box/disks_nanite + name = "nanite program disks box" + illustration = "disk_kit" + +/obj/item/storage/box/disks_nanite/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/nanite_program(src) + +// Ordinary survival box +/obj/item/storage/box/survival/PopulateContents() + new /obj/item/clothing/mask/breath(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/survival/radio/PopulateContents() + ..() // we want the survival stuff too. + new /obj/item/radio/off(src) + +/obj/item/storage/box/survival_mining/PopulateContents() + new /obj/item/clothing/mask/gas/explorer(src) + new /obj/item/crowbar/red(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +// Engineer survival box +/obj/item/storage/box/engineer/PopulateContents() + new /obj/item/clothing/mask/breath(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/engineer/radio/PopulateContents() + ..() // we want the regular items too. + new /obj/item/radio/off(src) + +// Syndie survival box +/obj/item/storage/box/syndie/PopulateContents() + new /obj/item/clothing/mask/gas/syndicate(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +// Security survival box +/obj/item/storage/box/security/PopulateContents() + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/security/radio/PopulateContents() + ..() // we want the regular stuff too + new /obj/item/radio/off(src) + +/obj/item/storage/box/seclooking + icon_state = "secbox" + illustration = null + +/obj/item/storage/box/cells + name = "box of powercells" + desc = "Contains powercells." + illustration = "power_cell" + +/obj/item/storage/box/ammoshells + name = "box of loose ammo" + desc = "Contains loose ammo." + illustration = "loose_ammo" + +/obj/item/storage/box/otwo + name = "box of o2 supplies" + desc = "Contains o2 supplies." + illustration = "02" + +/obj/item/storage/box/otwo/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + +/obj/item/storage/box/gloves + name = "box of latex gloves" + desc = "Contains sterile latex gloves." + illustration = "latex" + +/obj/item/storage/box/gloves/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/gloves/color/latex(src) + +/obj/item/storage/box/masks + name = "box of sterile masks" + desc = "This box contains sterile medical masks." + illustration = "sterile" + +/obj/item/storage/box/masks/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/mask/surgical(src) + +/obj/item/storage/box/syringes + name = "box of syringes" + desc = "A box full of syringes." + illustration = "syringe" + +/obj/item/storage/box/syringes/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/syringe(src) + +/obj/item/storage/box/medipens + name = "box of medipens" + desc = "A box full of epinephrine MediPens." + illustration = "syringe" + +/obj/item/storage/box/medipens/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/hypospray/medipen(src) + +/obj/item/storage/box/medipens/utility + name = "stimpack value kit" + desc = "A box with several stimpack medipens for the economical miner." + illustration = "syringe" + +/obj/item/storage/box/medipens/utility/PopulateContents() + ..() // includes regular medipens. + for(var/i in 1 to 5) + new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) + +/obj/item/storage/box/beakers + name = "box of beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker( src ) + +/obj/item/storage/box/beakers/bluespace + name = "box of bluespace beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/bluespace/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/medsprays + name = "box of medical sprayers" + desc = "A box full of medical sprayers, with unscrewable caps and precision spray heads." + +/obj/item/storage/box/medsprays/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/medspray( src ) + +/obj/item/storage/box/injectors + name = "box of DNA injectors" + desc = "This box contains injectors, it seems." + +/obj/item/storage/box/injectors/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/dnainjector/h2m(src) + for(var/i in 1 to 3) + new /obj/item/dnainjector/m2h(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 = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/flashbangs/PopulateContents() + for(var/i in 1 to 7) + 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 = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/flashes/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/assembly/flash/handheld(src) + +/obj/item/storage/box/wall_flash + name = "wall-mounted flash kit" + desc = "This box contains everything necessary to build a wall-mounted flash. WARNING: Flashes can cause serious eye damage, protective eyewear is required." + illustration = "flashbang" + +/obj/item/storage/box/wall_flash/PopulateContents() + var/id = rand(1000, 9999) + // FIXME what if this conflicts with an existing one? + + new /obj/item/wallframe/button(src) + new /obj/item/electronics/airlock(src) + var/obj/item/assembly/control/flasher/remote = new(src) + remote.id = id + var/obj/item/wallframe/flasher/frame = new(src) + frame.id = id + new /obj/item/assembly/flash/handheld(src) + new /obj/item/screwdriver(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." + illustration = "flashbang" + +/obj/item/storage/box/teargas/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/chem_grenade/teargas(src) + +/obj/item/storage/box/emps + name = "box of emp grenades" + desc = "A box with 5 emp grenades." + illustration = "flashbang" + +/obj/item/storage/box/emps/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/grenade/empgrenade(src) + +/obj/item/storage/box/trackimp + name = "boxed tracking implant kit" + desc = "Box full of scum-bag tracking utensils." + illustration = "implant" + +/obj/item/storage/box/trackimp/PopulateContents() + for(var/i in 1 to 4) + 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." + illustration = "implant" + +/obj/item/storage/box/minertracker/PopulateContents() + for(var/i in 1 to 3) + 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 = "boxed chemical implant kit" + desc = "Box of stuff used to implant chemicals." + illustration = "implant" + +/obj/item/storage/box/chemimp/PopulateContents() + for(var/i in 1 to 5) + 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." + illustration = "implant" + +/obj/item/storage/box/exileimp/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/implantcase/exile(src) + new /obj/item/implanter(src) + +/obj/item/storage/box/bodybags + name = "body bags" + desc = "The label indicates that it contains body bags." + illustration = "bodybags" + +/obj/item/storage/box/bodybags/PopulateContents() + ..() + for(var/i in 1 to 7) + new /obj/item/bodybag(src) + +/obj/item/storage/box/rxglasses + name = "box of prescription glasses" + desc = "This box contains nerd glasses." + illustration = "glasses" + +/obj/item/storage/box/rxglasses/PopulateContents() + for(var/i in 1 to 7) + 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." + +/obj/item/storage/box/drinkingglasses/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + +/obj/item/storage/box/condimentbottles + name = "box of condiment bottles" + desc = "It has a large ketchup smear on it." + +/obj/item/storage/box/condimentbottles/PopulateContents() + for(var/i in 1 to 6) + 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." + +/obj/item/storage/box/cups/PopulateContents() + for(var/i in 1 to 7) + 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 = "donkpocketbox" + illustration=null + +/obj/item/storage/box/donkpockets/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket)) + +/obj/item/storage/box/donkpockets/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + +/obj/item/storage/box/donkpockets/donkpocketspicy + name = "box of spicy-flavoured donk-pockets" + icon_state = "donkpocketboxspicy" + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/spicy + +/obj/item/storage/box/donkpockets/donkpocketspicy/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/spicy)) + +/obj/item/storage/box/donkpockets/donkpocketspicy/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket/spicy(src) + +/obj/item/storage/box/donkpockets/donkpocketteriyaki + name = "box of teriyaki-flavoured donk-pockets" + icon_state = "donkpocketboxteriyaki" + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki + +/obj/item/storage/box/donkpockets/donkpocketteriyaki/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/teriyaki)) + +/obj/item/storage/box/donkpockets/donkpocketteriyaki/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki(src) + +/obj/item/storage/box/donkpockets/donkpocketpizza + name = "box of pizza-flavoured donk-pockets" + icon_state = "donkpocketboxpizza" + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/pizza + +/obj/item/storage/box/donkpockets/donkpocketpizza/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/pizza)) + +/obj/item/storage/box/donkpockets/donkpocketpizza/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket/pizza(src) + +/obj/item/storage/box/donkpockets/donkpocketgondola + name = "box of gondola-flavoured donk-pockets" + icon_state = "donkpocketboxgondola" + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/gondola + +/obj/item/storage/box/donkpockets/donkpocketgondola/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/gondola)) + +/obj/item/storage/box/donkpockets/donkpocketgondola/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket/gondola(src) + +/obj/item/storage/box/donkpockets/donkpocketberry + name = "box of berry-flavoured donk-pockets" + icon_state = "donkpocketboxberry" + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/berry + +/obj/item/storage/box/donkpockets/donkpocketberry/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/berry)) + +/obj/item/storage/box/donkpockets/donkpocketberry/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket/berry(src) + +/obj/item/storage/box/donkpockets/donkpockethonk + name = "box of banana-flavoured donk-pockets" + icon_state = "donkpocketboxbanana" + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket/honk + +/obj/item/storage/box/donkpockets/donkpockethonk/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket/honk)) + +/obj/item/storage/box/donkpockets/donkpockethonk/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket/honk(src) + +/obj/item/storage/box/monkeycubes + name = "monkey cube box" + desc = "Drymate brand monkey cubes. Just add water!" + icon_state = "monkeycubebox" + illustration = null + +/obj/item/storage/box/monkeycubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 7 + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/monkeycubes/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/food/snacks/monkeycube(src) + +/obj/item/storage/box/ids + name = "box of spare IDs" + desc = "Has so many empty IDs." + illustration = "id" + +/obj/item/storage/box/ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id(src) + +//Some spare PDAs in a box +/obj/item/storage/box/PDAs + name = "spare PDAs" + desc = "A box of spare PDA microcomputers." + illustration = "pda" + +/obj/item/storage/box/PDAs/PopulateContents() + new /obj/item/pda(src) + new /obj/item/pda(src) + new /obj/item/pda(src) + new /obj/item/pda(src) + new /obj/item/cartridge/head(src) + + var/newcart = pick( /obj/item/cartridge/engineering, + /obj/item/cartridge/security, + /obj/item/cartridge/medical, + /obj/item/cartridge/signal/toxins, + /obj/item/cartridge/quartermaster) + new newcart(src) + +/obj/item/storage/box/silver_ids + name = "box of spare silver IDs" + desc = "Shiny IDs for important people." + illustration = "id" + +/obj/item/storage/box/silver_ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id/silver(src) + +/obj/item/storage/box/prisoner + name = "box of prisoner IDs" + desc = "Take away their last shred of dignity, their name." + illustration = "id" + +/obj/item/storage/box/prisoner/PopulateContents() + ..() + 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 = "box of PDA security cartridges" + desc = "A box full of PDA cartridges used by Security." + illustration = "pda" + +/obj/item/storage/box/seccarts/PopulateContents() + new /obj/item/cartridge/detective(src) + for(var/i in 1 to 6) + new /obj/item/cartridge/security(src) + +/obj/item/storage/box/firingpins + name = "box of standard firing pins" + desc = "A box full of standard firing pins, to allow newly-developed firearms to operate." + illustration = "firing_pins" + +/obj/item/storage/box/firingpins/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/firing_pin(src) + +/obj/item/storage/box/lasertagpins + name = "box of laser tag firing pins" + desc = "A box full of laser tag firing pins, to allow newly-developed firearms to require wearing brightly coloured plastic armor before being able to be used." + illustration = "firing_pins" + +/obj/item/storage/box/lasertagpins/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/firing_pin/tag/red(src) + new /obj/item/firing_pin/tag/blue(src) + +/obj/item/storage/box/handcuffs + name = "box of spare handcuffs" + desc = "A box full of handcuffs." + icon_state = "secbox" + illustration = "handcuff" + +/obj/item/storage/box/handcuffs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs(src) + +/obj/item/storage/box/zipties + name = "box of spare zipties" + desc = "A box full of zipties." + icon_state = "secbox" + illustration = "handcuff" + +/obj/item/storage/box/zipties/PopulateContents() + for(var/i in 1 to 7) + 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 = "alienbox" + illustration = "handcuff" + +/obj/item/storage/box/alienhandcuffs/PopulateContents() + 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 = "syndiebox" + +/obj/item/storage/box/fakesyndiesuit/PopulateContents() + new /obj/item/clothing/head/syndicatefake(src) + new /obj/item/clothing/suit/syndicatefake(src) + +/obj/item/storage/box/mousetraps + name = "box of Pest-B-Gon mousetraps" + desc = "Keep out of reach of children." + illustration = "mousetraps" + +/obj/item/storage/box/mousetraps/PopulateContents() + for(var/i in 1 to 6) + 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." + illustration = "pillbox" + +/obj/item/storage/box/pillbottles/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/storage/pill_bottle(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" + +/obj/item/storage/box/snappops/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/toy/snappop)) + STR.max_items = 8 + +/obj/item/storage/box/snappops/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) + +/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" + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BELT + +/obj/item/storage/box/matches/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.can_hold = typecacheof(list(/obj/item/match)) + +/obj/item/storage/box/matches/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) + +/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/match)) + W.matchignite() + +/obj/item/storage/box/lights + name = "box of replacement bulbs" + icon = 'icons/obj/storage.dmi' + illustration = "light" + desc = "This box is shaped on the inside so that only light tubes and bulbs fit." + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + foldable = /obj/item/stack/sheet/cardboard //BubbleWrap + +/obj/item/storage/box/lights/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 21 + STR.can_hold = typecacheof(list(/obj/item/light/tube, /obj/item/light/bulb)) + STR.max_combined_w_class = 21 + STR.click_gather = TRUE + +/obj/item/storage/box/lights/bulbs/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/bulb(src) + +/obj/item/storage/box/lights/tubes + name = "box of replacement tubes" + illustration = "lighttube" + +/obj/item/storage/box/lights/tubes/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/tube(src) + +/obj/item/storage/box/lights/mixed + name = "box of replacement lights" + illustration = "lightmixed" + +/obj/item/storage/box/lights/mixed/PopulateContents() + for(var/i in 1 to 14) + new /obj/item/light/tube(src) + for(var/i in 1 to 7) + new /obj/item/light/bulb(src) + +/obj/item/storage/box/deputy + name = "box of deputy armbands" + desc = "To be issued to those authorized to act as deputy of security." + +/obj/item/storage/box/deputy/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/accessory/armband/deputy(src) + +/obj/item/storage/box/metalfoam + name = "box of metal foam grenades" + desc = "To be used to rapidly seal hull breaches." + illustration = "flashbang" + +/obj/item/storage/box/metalfoam/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/chem_grenade/metalfoam(src) + +/obj/item/storage/box/smart_metal_foam + name = "box of smart metal foam grenades" + desc = "Used to rapidly seal hull breaches. This variety conforms to the walls of its area." + illustration = "flashbang" + +/obj/item/storage/box/smart_metal_foam/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/chem_grenade/smart_metal_foam(src) + +/obj/item/storage/box/hug + name = "box of hugs" + desc = "A special box for sensitive people." + icon_state = "hugbox" + illustration = "heart" + 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].") + +/////clown box & honkbot assembly +/obj/item/storage/box/clown + name = "clown box" + desc = "A colorful cardboard box for the clown" + illustration = "clown" + +/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) + if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) + if(contents.len) //prevent accidently deleting contents + to_chat(user, "You need to empty [src] out first!") + return + if(!user.temporarilyRemoveItemFromInventory(I)) + return + qdel(I) + to_chat(user, "You add some wheels to the [src]! You've got an honkbot assembly now! Honk!") + var/obj/item/bot_assembly/honkbot/A = new + qdel(src) + user.put_in_hands(A) + else + return ..() + +////// +/obj/item/storage/box/hug/medical/PopulateContents() + new /obj/item/stack/medical/bruise_pack(src) + new /obj/item/stack/medical/ointment(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + +// Clown survival box +/obj/item/storage/box/hug/survival/PopulateContents() + new /obj/item/clothing/mask/breath(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/rubbershot + name = "box of rubber shots" + desc = "A box full of rubber shots, designed for riot shotguns." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/rubbershot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/rubbershot(src) + +/obj/item/storage/box/lethalshot + name = "box of buckshot (Lethal)" + desc = "A box full of lethal shots, designed for riot shotguns." + icon_state = "lethalshot_box" + illustration = null + +/obj/item/storage/box/lethalshot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/buckshot(src) + +/obj/item/storage/box/beanbag + name = "box of beanbags" + desc = "A box full of beanbag shells." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/beanbag/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/ammo_casing/shotgun/beanbag(src) + +/obj/item/storage/box/lethalslugs + name = "box of 12g shotgun slugs" + desc = "A box full of lethal 12g slug, designed for riot shotguns." + icon_state = "12g_box" + illustration = null + +/obj/item/storage/box/lethalslugs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun(src) + +/obj/item/storage/box/stunslug + name = "box of stun slugs" + desc = "A box full of stun 12g slugs." + icon_state = "stunslug_box" + illustration = null + +/obj/item/storage/box/stunslug/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/stunslug(src) + +/obj/item/storage/box/techsslug + name = "box of tech shotgun shells" + desc = "A box full of tech shotgun shells." + icon_state = "techslug_box" + illustration = null + +/obj/item/storage/box/techsslug/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/techshell(src) + +/obj/item/storage/box/fireshot + name = "box of incendiary ammo" + desc = "A box full of incendiary ammo." + icon_state = "fireshot_box" + illustration = null + +/obj/item/storage/box/fireshot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/incendiary(src) + +/obj/item/storage/box/actionfigure + name = "box of action figures" + desc = "The latest set of collectable action figures." + icon_state = "box" + +/obj/item/storage/box/actionfigure/PopulateContents() + for(var/i in 1 to 4) + var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) + new randomFigure(src) + +/obj/item/storage/box/mechfigures + name = "box of mech figures" + desc = "The latest set of collectable mech figures." + icon_state = "box" + +/obj/item/storage/box/mechfigures/PopulateContents() + for(var/i in 1 to 4) + var/randomFigure = pick(subtypesof(/obj/item/toy/prize/)) + new randomFigure(src) + + + + +/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 + /// A list of all available papersack reskins + var/list/papersack_designs = list() + +/obj/item/storage/box/papersack/Initialize(mapload) + . = ..() + papersack_designs = sortList(list( + "None" = image(icon = src.icon, icon_state = "paperbag_None"), + "NanotrasenStandard" = image(icon = src.icon, icon_state = "paperbag_NanotrasenStandard"), + "SyndiSnacks" = image(icon = src.icon, icon_state = "paperbag_SyndiSnacks"), + "Heart" = image(icon = src.icon, icon_state = "paperbag_Heart"), + "SmileyFace" = image(icon = src.icon, icon_state = "paperbag_SmileyFace") + )) + +/obj/item/storage/box/papersack/update_icon_state() + if(contents.len == 0) + 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)) + var/choice = show_radial_menu(user, src , papersack_designs, custom_check = CALLBACK(src, .proc/check_menu, user, W), radius = 36, require_near = TRUE) + if(!choice) + return FALSE + if(icon_state == "paperbag_[choice]") + return FALSE + switch(choice) + if("None") + desc = "A sack neatly crafted out of paper." + if("NanotrasenStandard") + desc = "A standard Kinaris paper lunch sack for loyal employees on the go." + if("SyndiSnacks") + 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("SmileyFace") + desc = "A paper sack with a crude smile etched onto the side." + else + return FALSE + to_chat(user, "You make some modifications to [src] using your pen.") + icon_state = "paperbag_[choice]" + item_state = "paperbag_[choice]" + return FALSE + else if(W.is_sharp()) + if(!contents.len) + if(item_state == "paperbag_None") + user.show_message("You cut eyeholes into [src].", MSG_VISUAL) + new /obj/item/clothing/head/papersack(user.loc) + qdel(src) + return FALSE + else if(item_state == "paperbag_SmileyFace") + user.show_message("You cut eyeholes into [src] and modify the design.", MSG_VISUAL) + new /obj/item/clothing/head/papersack/smiley(user.loc) + qdel(src) + return FALSE + return ..() + +/** + * check_menu: Checks if we are allowed to interact with a radial menu + * + * Arguments: + * * user The mob interacting with a menu + * * P The pen used to interact with a menu + */ +/obj/item/storage/box/papersack/proc/check_menu(mob/user, obj/item/pen/P) + if(!istype(user)) + return FALSE + if(user.incapacitated()) + return FALSE + if(contents.len) + to_chat(user, "You can't modify [src] with items still inside!") + return FALSE + if(!P || !user.is_holding(P)) + to_chat(user, "You need a pen to modify [src]!") + return FALSE + return TRUE + +/obj/item/storage/box/ingredients //This box is for the randomly chosen version the chef spawns with, it shouldn't actually exist. + name = "ingredients box" + illustration = "fruit" + var/theme_name + +/obj/item/storage/box/ingredients/Initialize() + . = ..() + if(theme_name) + name = "[name] ([theme_name])" + desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." + item_state = "syringe_kit" + +/obj/item/storage/box/ingredients/wildcard + theme_name = "wildcard" + +/obj/item/storage/box/ingredients/wildcard/PopulateContents() + for(var/i in 1 to 7) + var/randomFood = pick(/obj/item/reagent_containers/food/snacks/grown/chili, + /obj/item/reagent_containers/food/snacks/grown/tomato, + /obj/item/reagent_containers/food/snacks/grown/carrot, + /obj/item/reagent_containers/food/snacks/grown/potato, + /obj/item/reagent_containers/food/snacks/grown/potato/sweet, + /obj/item/reagent_containers/food/snacks/grown/apple, + /obj/item/reagent_containers/food/snacks/chocolatebar, + /obj/item/reagent_containers/food/snacks/grown/cherries, + /obj/item/reagent_containers/food/snacks/grown/banana, + /obj/item/reagent_containers/food/snacks/grown/cabbage, + /obj/item/reagent_containers/food/snacks/grown/soybeans, + /obj/item/reagent_containers/food/snacks/grown/corn, + /obj/item/reagent_containers/food/snacks/grown/mushroom/plumphelmet, + /obj/item/reagent_containers/food/snacks/grown/mushroom/chanterelle) + new randomFood(src) + +/obj/item/storage/box/ingredients/fiesta + theme_name = "fiesta" + +/obj/item/storage/box/ingredients/fiesta/PopulateContents() + new /obj/item/reagent_containers/food/snacks/tortilla(src) + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + +/obj/item/storage/box/ingredients/italian + theme_name = "italian" + +/obj/item/storage/box/ingredients/italian/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + new /obj/item/reagent_containers/food/drinks/bottle/wine(src) + +/obj/item/storage/box/ingredients/vegetarian + theme_name = "vegetarian" + +/obj/item/storage/box/ingredients/vegetarian/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/carrot(src) + new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + +/obj/item/storage/box/ingredients/american + theme_name = "american" + +/obj/item/storage/box/ingredients/american/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + +/obj/item/storage/box/ingredients/fruity + theme_name = "fruity" + +/obj/item/storage/box/ingredients/fruity/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) + new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) + +/obj/item/storage/box/ingredients/sweets + theme_name = "sweets" + +/obj/item/storage/box/ingredients/sweets/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/cherries(src) + new /obj/item/reagent_containers/food/snacks/grown/banana(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + +/obj/item/storage/box/ingredients/delights + theme_name = "delights" + +/obj/item/storage/box/ingredients/delights/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) + new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) + new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/berries(src) + +/obj/item/storage/box/ingredients/grains + theme_name = "grains" + +/obj/item/storage/box/ingredients/grains/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/grown/oat(src) + new /obj/item/reagent_containers/food/snacks/grown/wheat(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/honeycomb(src) + new /obj/item/seeds/poppy(src) + +/obj/item/storage/box/ingredients/carnivore + theme_name = "carnivore" + +/obj/item/storage/box/ingredients/carnivore/PopulateContents() + new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) + new /obj/item/reagent_containers/food/snacks/spidereggs(src) + new /obj/item/reagent_containers/food/snacks/carpmeat(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + +/obj/item/storage/box/ingredients/exotic + theme_name = "exotic" + +/obj/item/storage/box/ingredients/exotic/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/carpmeat(src) + new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) + new /obj/item/reagent_containers/food/snacks/grown/cabbage(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + +/obj/item/storage/box/emptysandbags + name = "box of empty sandbags" + +/obj/item/storage/box/emptysandbags/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/emptysandbag(src) + +/obj/item/storage/box/rndboards + name = "\proper the liberator's legacy" + desc = "A box containing a gift for worthy golems." + +/obj/item/storage/box/rndboards/PopulateContents() + new /obj/item/circuitboard/machine/protolathe(src) + new /obj/item/circuitboard/machine/destructive_analyzer(src) + new /obj/item/circuitboard/machine/circuit_imprinter(src) + new /obj/item/circuitboard/computer/rdconsole(src) + +/obj/item/storage/box/silver_sulf + name = "box of silver sulfadiazine patches" + desc = "Contains patches used to treat burns." + +/obj/item/storage/box/silver_sulf/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + +/obj/item/storage/box/fountainpens + name = "box of fountain pens" + +/obj/item/storage/box/fountainpens/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/pen/fountain(src) + +/obj/item/storage/box/holy_grenades + name = "box of holy hand grenades" + desc = "Contains several grenades used to rapidly purge heresy." + illustration = "flashbang" + +/obj/item/storage/box/holy_grenades/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/chem_grenade/holy(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/PopulateContents() + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/manipulator(src) + new /obj/item/stock_parts/manipulator(src) + new /obj/item/stock_parts/manipulator(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/matter_bin(src) + new /obj/item/stock_parts/matter_bin(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." + icon_state = "syndiebox" + +/obj/item/storage/box/stockparts/deluxe/PopulateContents() + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/manipulator/femto(src) + new /obj/item/stock_parts/manipulator/femto(src) + new /obj/item/stock_parts/manipulator/femto(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + +//Colored boxes. +/obj/item/storage/box/green + icon_state = "box_green" + illustration = null + +/obj/item/storage/box/blue + icon_state = "box_blue" + illustration = null + +/obj/item/storage/box/purple + icon_state = "box_purple" + illustration = null + +/obj/item/storage/box/red + icon_state = "box_red" + illustration = null + +/obj/item/storage/box/yellow + icon_state = "box_yellow" + illustration = null + +/obj/item/storage/box/brown + icon_state = "box_brown" + illustration = null + +/obj/item/storage/box/pink + icon_state = "box_pink" + illustration = null + +/obj/item/storage/box/mre //base MRE type. + name = "Kinaris MRE Ration Kit Menu 0" + desc = "A package containing food suspended in an outdated bluespace pocket which lasts for centuries. If you're lucky you may even be able to enjoy the meal without getting food poisoning." + icon_state = "mre" + illustration = null + var/can_expire = TRUE + var/spawner_chance = 2 + var/expiration_date + var/expiration_date_min = 2300 + var/expiration_date_max = 2700 + +/obj/item/storage/box/mre/Initialize() + . = ..() + if(can_expire) + expiration_date = rand(expiration_date_min, expiration_date_max) + desc += "\nAn expiry date is listed on it. It reads: [expiration_date]" + var/spess_current_year = GLOB.year_integer + 540 + if(expiration_date < spess_current_year) + var/gross_risk = min(round(spess_current_year - expiration_date * 0.1), 1) + var/toxic_risk = min(round(spess_current_year - expiration_date * 0.01), 1) + for(var/obj/item/reagent_containers/food/snacks/S in contents) + if(prob(gross_risk)) + ENABLE_BITFIELD(S.foodtype, GROSS) + if(prob(toxic_risk)) + ENABLE_BITFIELD(S.foodtype, TOXIC) + +/obj/item/storage/box/mre/menu1 + name = "\improper Kinaris MRE Ration Kit Menu 1" + +/obj/item/storage/box/mre/menu1/safe + desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." + spawner_chance = 0 + can_expire = FALSE + +/obj/item/storage/box/mre/menu1/PopulateContents() + new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) + new /obj/item/reagent_containers/food/snacks/breadslice/creamcheese(src) + new /obj/item/reagent_containers/food/condiment/pack/ketchup(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +/obj/item/storage/box/mre/menu2 + name = "\improper Kinaris MRE Ration Kit Menu 2" + +/obj/item/storage/box/mre/menu2/safe + spawner_chance = 0 + desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." + can_expire = FALSE + +/obj/item/storage/box/mre/menu2/PopulateContents() + new /obj/item/reagent_containers/food/snacks/omelette(src) + new /obj/item/reagent_containers/food/snacks/meat/cutlet/plain(src) + new /obj/item/reagent_containers/food/snacks/fries(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +/obj/item/storage/box/mre/menu3 + name = "\improper Kinaris MRE Ration Kit Menu 3" + desc = "The holy grail of MREs. This item contains the fabled MRE pizza, spicy nachos and a sample of coffee instant type 2. Any NT employee lucky enough to get their hands on one of these is truly blessed." + icon_state = "menu3" + can_expire = FALSE //always fresh, never expired. + spawner_chance = 1 + +/obj/item/storage/box/mre/menu3/PopulateContents() + new /obj/item/reagent_containers/food/snacks/pizzaslice/pepperoni(src) + new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) + new /obj/item/reagent_containers/food/snacks/cubannachos(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + new /obj/item/reagent_containers/food/drinks/coffee/type2(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +/obj/item/storage/box/mre/menu4 + name = "\improper Kinaris MRE Ration Kit Menu 4" + +/obj/item/storage/box/mre/menu4/safe + spawner_chance = 0 + desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." + can_expire = FALSE + +/obj/item/storage/box/mre/menu4/PopulateContents() + if(prob(66)) + new /obj/item/reagent_containers/food/snacks/salad/boiledrice(src) + else + new /obj/item/reagent_containers/food/snacks/salad/ricebowl(src) + new /obj/item/reagent_containers/food/snacks/burger/tofu(src) + new /obj/item/reagent_containers/food/snacks/salad/fruit(src) + new /obj/item/reagent_containers/food/snacks/cracker(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +//Where do I put this? +/obj/item/secbat + name = "Secbat box" + desc = "Contained inside is a secbat for use with law enforcement." + icon = 'icons/obj/storage.dmi' + icon_state = "box" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + +/obj/item/secbat/attack_self(mob/user) + new /mob/living/simple_animal/hostile/retaliate/bat/secbat(user.loc) + to_chat(user, "You open the box, releasing the secbat!") + var/obj/item/stack/sheet/cardboard/I = new(user.drop_location()) + qdel(src) + user.put_in_hands(I) diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index 0129eeb3..92b0036e 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -1,240 +1,240 @@ - -#define SOURCE_PORTAL 1 -#define DESTINATION_PORTAL 2 - -/* Teleportation devices. - * Contains: - * Locator - * Hand-tele - */ - -/* - * Locator - */ -/obj/item/locator - name = "bluespace locator" - desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - var/temp = null - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=400) - -/obj/item/locator/attack_self(mob/user) - user.set_machine(src) - var/dat - if (temp) - dat = "[temp]

Clear" - else - dat = {" -Persistent Signal Locator


-Refresh"} - user << browse(dat, "window=radio") - onclose(user, "radio") - return - -/obj/item/locator/Topic(href, href_list) - ..() - if (usr.stat || usr.restrained()) - return - var/turf/current_location = get_turf(usr)//What turf is the user on? - if(!current_location || is_centcom_level(current_location.z))//If turf was not found or they're on CentCom - to_chat(usr, "[src] is malfunctioning.") - return - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc))) - usr.set_machine(src) - if (href_list["refresh"]) - temp = "Persistent Signal Locator
" - var/turf/sr = get_turf(src) - - if (sr) - temp += "Beacon Signals:
" - for(var/obj/item/beacon/W in GLOB.teleportbeacons) - if (!W.renamed) - continue - var/turf/tr = get_turf(W) - if (tr.z == sr.z && tr) - var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - if (direct < 5) - direct = "very strong" - else - if (direct < 10) - direct = "strong" - else - if (direct < 20) - direct = "weak" - else - direct = "very weak" - temp += "[W.name]-[dir2text(get_dir(sr, tr))]-[direct]
" - - temp += "Implant Signals:
" - for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) - if (!isliving(W.imp_in)) - continue - var/mob/living/M = W.imp_in - if (M.stat == DEAD) - if (M.timeofdeath + W.lifespan_postmortem < world.time) - continue - - var/turf/tr = get_turf(M) - if (tr.z == sr.z && tr) - var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - if (direct < 20) - if (direct < 5) - direct = "very strong" - else - if (direct < 10) - direct = "strong" - else - direct = "weak" - temp += "[W.imp_in.name]-[dir2text(get_dir(sr, tr))]-[direct]
" - - temp += "You are at \[[sr.x],[sr.y],[sr.z]\] in orbital coordinates.

Refresh
" - else - temp += "Processing Error: Unable to locate orbital position.
" - else - if (href_list["temp"]) - temp = null - if (ismob(src.loc)) - attack_self(src.loc) - else - for(var/mob/M in viewers(1, src)) - if (M.client) - src.attack_self(M) - return - - -/* - * 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" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - materials = list(MAT_METAL=10000) - 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/list/active_portal_pairs - var/max_portal_pairs = 3 - var/atmos_link_override - -/obj/item/hand_tele/Initialize() - . = ..() - active_portal_pairs = list() - -/obj/item/hand_tele/pre_attack(atom/target, mob/user, params) - if(try_dispel_portal(target, user)) - return FALSE - return ..() - -/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user) - if(is_parent_of_portal(target)) - qdel(target) - to_chat(user, "You dispel [target] with \the [src]!") - return TRUE - return FALSE - -/obj/item/hand_tele/afterattack(atom/target, mob/user) - try_dispel_portal(target, user) - . = ..() - -/obj/item/hand_tele/attack_self(mob/user) - var/turf/current_location = get_turf(user)//What turf is the user on? - var/area/current_area = current_location.loc - if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf - to_chat(user, "\The [src] is malfunctioning.") - return - var/list/L = list( ) - for(var/obj/machinery/computer/teleporter/com in GLOB.machines) - if(com.target) - var/area/A = get_area(com.target) - if(!A || A.noteleport) - continue - if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) - L["[get_area(com.target)] (Active)"] = com.target - else - L["[get_area(com.target)] (Inactive)"] = com.target - var/list/turfs = list( ) - for(var/turf/T in urange(10, orange=1)) - 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 - var/area/A = T.loc - if(A.noteleport) - continue - 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.get_active_held_item() != src || user.incapacitated()) - return - if(active_portal_pairs.len >= max_portal_pairs) - user.show_message("\The [src] is recharging!") - return - var/atom/T = L[t1] - var/implantcheckmate = FALSE - if(isliving(T)) - var/mob/living/M = T - if(!locate(/obj/item/implant/tracking) in M.implants) //The user was too slow and let the target mob's tracking implant expire or get removed. - implantcheckmate = TRUE - var/area/A = get_area(T) - if(A.noteleport || implantcheckmate) - to_chat(user, "\The [src] is malfunctioning.") - return - current_location = get_turf(user) //Recheck. - current_area = current_location.loc - if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf - to_chat(user, "\The [src] is malfunctioning.") - return - user.show_message("Locked In.", 2) - var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), src, 300, 1, null, atmos_link_override) - if(!(LAZYLEN(created) == 2)) - return - try_move_adjacent(created[1]) - active_portal_pairs[created[1]] = created[2] - var/obj/effect/portal/c1 = created[1] - var/obj/effect/portal/c2 = created[2] - investigate_log("was used by [key_name(user)] at [AREACOORD(user)] to create a portal pair with destinations [AREACOORD(c1)] and [AREACOORD(c2)].", INVESTIGATE_PORTAL) - add_fingerprint(user) - -/obj/item/hand_tele/proc/on_portal_destroy(obj/effect/portal/P) - active_portal_pairs -= P //If this portal pair is made by us it'll be erased along with the other portal by the portal. - -/obj/item/hand_tele/proc/is_parent_of_portal(obj/effect/portal/P) - if(!istype(P)) - return FALSE - if(active_portal_pairs[P]) - return SOURCE_PORTAL - for(var/i in active_portal_pairs) - if(active_portal_pairs[i] == P) - return DESTINATION_PORTAL - return FALSE - -/obj/item/hand_tele/suicide_act(mob/user) - if(iscarbon(user)) - user.visible_message("[user] is creating a weak portal and sticking [user.p_their()] head through! It looks like [user.p_theyre()] trying to commit suicide!") - var/mob/living/carbon/itemUser = user - var/obj/item/bodypart/head/head = itemUser.get_bodypart(BODY_ZONE_HEAD) - if(head) - head.drop_limb() - var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_SPACE_RUINS, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING)) - head.forceMove(locate(rand(1, world.maxx), rand(1, world.maxy), pick(safeLevels))) - itemUser.visible_message("The portal snaps closed taking [user]'s head with it!") - else - itemUser.visible_message("[user] looks even further depressed as they realize they do not have a head...and suddenly dies of shame!") - return (BRUTELOSS) + +#define SOURCE_PORTAL 1 +#define DESTINATION_PORTAL 2 + +/* Teleportation devices. + * Contains: + * Locator + * Hand-tele + */ + +/* + * Locator + */ +/obj/item/locator + name = "bluespace locator" + desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + var/temp = null + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=400) + +/obj/item/locator/attack_self(mob/user) + user.set_machine(src) + var/dat + if (temp) + dat = "[temp]

Clear" + else + dat = {" +Persistent Signal Locator
+Refresh"} + user << browse(dat, "window=radio") + onclose(user, "radio") + return + +/obj/item/locator/Topic(href, href_list) + ..() + if (usr.stat || usr.restrained()) + return + var/turf/current_location = get_turf(usr)//What turf is the user on? + if(!current_location || is_centcom_level(current_location.z))//If turf was not found or they're on CentCom + to_chat(usr, "[src] is malfunctioning.") + return + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc))) + usr.set_machine(src) + if (href_list["refresh"]) + temp = "Persistent Signal Locator
" + var/turf/sr = get_turf(src) + + if (sr) + temp += "Beacon Signals:
" + for(var/obj/item/beacon/W in GLOB.teleportbeacons) + if (!W.renamed) + continue + var/turf/tr = get_turf(W) + if (tr.z == sr.z && tr) + var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + if (direct < 5) + direct = "very strong" + else + if (direct < 10) + direct = "strong" + else + if (direct < 20) + direct = "weak" + else + direct = "very weak" + temp += "[W.name]-[dir2text(get_dir(sr, tr))]-[direct]
" + + temp += "Implant Signals:
" + for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) + if (!isliving(W.imp_in)) + continue + var/mob/living/M = W.imp_in + if (M.stat == DEAD) + if (M.timeofdeath + W.lifespan_postmortem < world.time) + continue + + var/turf/tr = get_turf(M) + if (tr.z == sr.z && tr) + var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + if (direct < 20) + if (direct < 5) + direct = "very strong" + else + if (direct < 10) + direct = "strong" + else + direct = "weak" + temp += "[W.imp_in.name]-[dir2text(get_dir(sr, tr))]-[direct]
" + + temp += "You are at \[[sr.x],[sr.y],[sr.z]\] in orbital coordinates.

Refresh
" + else + temp += "Processing Error: Unable to locate orbital position.
" + else + if (href_list["temp"]) + temp = null + if (ismob(src.loc)) + attack_self(src.loc) + else + for(var/mob/M in viewers(1, src)) + if (M.client) + src.attack_self(M) + return + + +/* + * 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" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + materials = list(MAT_METAL=10000) + 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/list/active_portal_pairs + var/max_portal_pairs = 3 + var/atmos_link_override + +/obj/item/hand_tele/Initialize() + . = ..() + active_portal_pairs = list() + +/obj/item/hand_tele/pre_attack(atom/target, mob/user, params) + if(try_dispel_portal(target, user)) + return FALSE + return ..() + +/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user) + if(is_parent_of_portal(target)) + qdel(target) + to_chat(user, "You dispel [target] with \the [src]!") + return TRUE + return FALSE + +/obj/item/hand_tele/afterattack(atom/target, mob/user) + try_dispel_portal(target, user) + . = ..() + +/obj/item/hand_tele/attack_self(mob/user) + var/turf/current_location = get_turf(user)//What turf is the user on? + var/area/current_area = current_location.loc + if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf + to_chat(user, "\The [src] is malfunctioning.") + return + var/list/L = list( ) + for(var/obj/machinery/computer/teleporter/com in GLOB.machines) + if(com.target) + var/area/A = get_area(com.target) + if(!A || A.noteleport) + continue + if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) + L["[get_area(com.target)] (Active)"] = com.target + else + L["[get_area(com.target)] (Inactive)"] = com.target + var/list/turfs = list( ) + for(var/turf/T in urange(10, orange=1)) + 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 + var/area/A = T.loc + if(A.noteleport) + continue + 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.get_active_held_item() != src || user.incapacitated()) + return + if(active_portal_pairs.len >= max_portal_pairs) + user.show_message("\The [src] is recharging!") + return + var/atom/T = L[t1] + var/implantcheckmate = FALSE + if(isliving(T)) + var/mob/living/M = T + if(!locate(/obj/item/implant/tracking) in M.implants) //The user was too slow and let the target mob's tracking implant expire or get removed. + implantcheckmate = TRUE + var/area/A = get_area(T) + if(A.noteleport || implantcheckmate) + to_chat(user, "\The [src] is malfunctioning.") + return + current_location = get_turf(user) //Recheck. + current_area = current_location.loc + if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf + to_chat(user, "\The [src] is malfunctioning.") + return + user.show_message("Locked In.", MSG_AUDIBLE) + var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), src, 300, 1, null, atmos_link_override) + if(!(LAZYLEN(created) == 2)) + return + try_move_adjacent(created[1]) + active_portal_pairs[created[1]] = created[2] + var/obj/effect/portal/c1 = created[1] + var/obj/effect/portal/c2 = created[2] + investigate_log("was used by [key_name(user)] at [AREACOORD(user)] to create a portal pair with destinations [AREACOORD(c1)] and [AREACOORD(c2)].", INVESTIGATE_PORTAL) + add_fingerprint(user) + +/obj/item/hand_tele/proc/on_portal_destroy(obj/effect/portal/P) + active_portal_pairs -= P //If this portal pair is made by us it'll be erased along with the other portal by the portal. + +/obj/item/hand_tele/proc/is_parent_of_portal(obj/effect/portal/P) + if(!istype(P)) + return FALSE + if(active_portal_pairs[P]) + return SOURCE_PORTAL + for(var/i in active_portal_pairs) + if(active_portal_pairs[i] == P) + return DESTINATION_PORTAL + return FALSE + +/obj/item/hand_tele/suicide_act(mob/user) + if(iscarbon(user)) + user.visible_message("[user] is creating a weak portal and sticking [user.p_their()] head through! It looks like [user.p_theyre()] trying to commit suicide!") + var/mob/living/carbon/itemUser = user + var/obj/item/bodypart/head/head = itemUser.get_bodypart(BODY_ZONE_HEAD) + if(head) + head.drop_limb() + var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_SPACE_RUINS, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING)) + head.forceMove(locate(rand(1, world.maxx), rand(1, world.maxy), pick(safeLevels))) + itemUser.visible_message("The portal snaps closed taking [user]'s head with it!") + else + itemUser.visible_message("[user] looks even further depressed as they realize they do not have a head...and suddenly dies of shame!") + return (BRUTELOSS) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 5b58d186..65835457 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -182,7 +182,7 @@ return src.add_fingerprint(user) if (src.bullets < 1) - user.show_message("*click*", 2) + user.show_message("*click*", MSG_AUDIBLE) playsound(src, "gun_dry_fire", 30, 1) return playsound(user, 'sound/weapons/gunshot.ogg', 100, 1) diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm index 553712a0..7d1ee27e 100644 --- a/code/game/objects/structures/loom.dm +++ b/code/game/objects/structures/loom.dm @@ -1,56 +1,81 @@ -//Loom, turns raw cotton and durathread into their respective fabrics. - -/obj/structure/loom - name = "loom" - desc = "A simple device used to weave cloth and other thread-based fabrics together into usable material." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "loom" - density = TRUE - anchored = TRUE - -/obj/structure/loom/attackby(obj/item/I, mob/user) - if (user.a_intent != INTENT_HELP) - return ..() - if (istype(I, /obj/item/stack/sheet)) - if (!anchored) - return to_chat(user, "You have to anchor [src] first!") - - var/obj/item/stack/sheet/W = I - if(W.is_fabric && W.amount > 1) - user.visible_message("", - "You start weaving the [W.name] through the loom.") - - if(W.use_tool(src, user, W.pull_effort)) - new W.loom_result(drop_location()) - to_chat(user, "You weave the [W.name] into a workable fabric.") - W.amount = (W.amount - 2) - if(W.amount < 1) - qdel(W) - else if(W.is_fabric) - to_chat("You need at least 2 [W.name] to loom into fabric!") - return - else - switch (I.tool_behaviour) - if (TOOL_SCREWDRIVER) - user.visible_message("[user] starts disassembling [src]...", - "You start disassembling [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 60)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - deconstruct(TRUE) - return - if (TOOL_WRENCH) - if (anchored) - to_chat(user, "You unsecure the [src].") - I.play_tool_sound(src) - anchored = FALSE - else - to_chat(user, "You secure the [src].") - I.play_tool_sound(src) - anchored = TRUE - return - return ..() - -/obj/structure/loom/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood (get_turf(src), 10) - qdel(src) +//Loom, turns raw cotton and durathread into their respective fabrics. + +/obj/structure/loom + name = "loom" + desc = "A simple device used to weave cloth and other thread-based fabrics together into usable material." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "loom" + density = TRUE + anchored = TRUE + +/obj/structure/loom/attackby(obj/item/I, mob/user) + if (user.a_intent != INTENT_HELP) + return ..() + if (istype(I, /obj/item/stack/sheet)) + if (!anchored) + return to_chat(user, "You have to anchor [src] first!") + + var/obj/item/stack/sheet/W = I + if(W.is_fabric && W.amount > 1) + user.visible_message("", + "You start weaving the [W.name] through the loom.") + + if(W.use_tool(src, user, W.pull_effort)) + new W.loom_result(drop_location()) + to_chat(user, "You weave the [W.name] into a workable fabric.") + W.amount = (W.amount - 2) + if(W.amount < 1) + qdel(W) + else if(W.is_fabric) + to_chat("You need at least 2 [W.name] to loom into fabric!") + return + else + switch (I.tool_behaviour) + if (TOOL_SCREWDRIVER) + user.visible_message("[user] starts disassembling [src]...", + "You start disassembling [src]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 60)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + deconstruct(TRUE) + return + if (TOOL_WRENCH) + if (anchored) + to_chat(user, "You unsecure the [src].") + I.play_tool_sound(src) + anchored = FALSE + else + to_chat(user, "You secure the [src].") + I.play_tool_sound(src) + anchored = TRUE + return + return ..() + +/obj/structure/loom/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood (get_turf(src), 10) + qdel(src) + + + +/*///Handles the weaving. +/obj/structure/loom/proc/weave(obj/item/stack/sheet/S, mob/user) + if(!istype(S) || !S.is_fabric) + return FALSE + if(!anchored) + user.show_message("The loom needs to be wrenched down.", MSG_VISUAL) + return FALSE + if(S.amount < FABRIC_PER_SHEET) + user.show_message("You need at least [FABRIC_PER_SHEET] units of fabric before using this.", 1) + return FALSE + user.show_message("You start weaving \the [S.name] through the loom..", MSG_VISUAL) + if(S.use_tool(src, user, S.pull_effort)) + if(S.amount >= FABRIC_PER_SHEET) + new S.loom_result(drop_location()) + S.use(FABRIC_PER_SHEET) + user.show_message("You weave \the [S.name] into a workable fabric.", MSG_VISUAL) + return TRUE + +/obj/structure/loom/unanchored + anchored = FALSE + +#undef FABRIC_PER_SHEET */ diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index a524e0c0..436adc47 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -65,7 +65,7 @@ /obj/item/clothing/head/helmet/space/hardsuit/proc/display_visor_message(var/msg) var/mob/wearer = loc if(msg && ishuman(wearer)) - wearer.show_message("[icon2html(src, wearer)][msg]", 1) + wearer.show_message("[icon2html(src, wearer)][msg]", MSG_VISUAL) /obj/item/clothing/head/helmet/space/hardsuit/rad_act(severity) . = ..() diff --git a/code/modules/hydroponics/grown/cotton.dm b/code/modules/hydroponics/grown/cotton.dm index 3fa4247f..a3ac7d33 100644 --- a/code/modules/hydroponics/grown/cotton.dm +++ b/code/modules/hydroponics/grown/cotton.dm @@ -32,7 +32,7 @@ var/cotton_name = "raw cotton" /obj/item/grown/cotton/attack_self(mob/user) - user.show_message("You pull some [cotton_name] out of the [name]!", 1) + user.show_message("You pull some [cotton_name] out of the [name]!", MSG_VISUAL) var/seed_modifier = 0 if(seed) seed_modifier = round(seed.potency / 25) diff --git a/code/modules/hydroponics/grown/pumpkin.dm b/code/modules/hydroponics/grown/pumpkin.dm index e26d1c1d..6254fb5b 100644 --- a/code/modules/hydroponics/grown/pumpkin.dm +++ b/code/modules/hydroponics/grown/pumpkin.dm @@ -28,8 +28,8 @@ wine_power = 20 /obj/item/reagent_containers/food/snacks/grown/pumpkin/attackby(obj/item/W as obj, mob/user as mob, params) - if(W.is_sharp()) - user.show_message("You carve a face into [src]!", 1) + if(W.get_sharpness()) + user.show_message("You carve a face into [src]!", MSG_VISUAL) new /obj/item/clothing/head/hardhat/pumpkinhead(user.loc) qdel(src) return diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm index 6df0f0cd..b120e592 100644 --- a/code/modules/hydroponics/grown/towercap.dm +++ b/code/modules/hydroponics/grown/towercap.dm @@ -51,7 +51,7 @@ /obj/item/grown/log/attackby(obj/item/W, mob/user, params) if(W.sharpness) - user.show_message("You make [plank_name] out of \the [src]!", 1) + user.show_message("You make [plank_name] out of \the [src]!", MSG_VISUAL) var/seed_modifier = 0 if(seed) seed_modifier = round(seed.potency / 25) diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index 896b9ad2..d08eb5ae 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -861,27 +861,27 @@ if(M.stat == DEAD) //F return if(M == H) - H.show_message("You cringe with pain as your body rings around you!", 2) + H.show_message("You cringe with pain as your body rings around you!", MSG_AUDIBLE) H.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) H.soundbang_act(2, 0, 100, 1) H.jitteriness += 7 var/distance = max(0,get_dist(get_turf(H),get_turf(M))) switch(distance) if(0 to 1) - M.show_message("GONG!", 2) + M.show_message("GONG!", MSG_AUDIBLE) M.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) M.soundbang_act(1, 0, 30, 3) M.confused += 10 M.jitteriness += 4 SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) if(2 to 3) - M.show_message("GONG!", 2) + M.show_message("GONG!", MSG_AUDIBLE) M.playsound_local(H, 'sound/effects/gong.ogg', 75, TRUE) M.soundbang_act(1, 0, 15, 2) M.jitteriness += 3 SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) else - M.show_message("GONG!", 2) + M.show_message("GONG!", MSG_AUDIBLE) M.playsound_local(H, 'sound/effects/gong.ogg', 50, TRUE) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index f3336344..8d2c75d7 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -1,431 +1,431 @@ -GLOBAL_LIST_INIT(department_radio_prefixes, list(":", ".")) - -GLOBAL_LIST_INIT(department_radio_keys, list( - // Location - MODE_KEY_R_HAND = MODE_R_HAND, - MODE_KEY_L_HAND = MODE_L_HAND, - MODE_KEY_INTERCOM = MODE_INTERCOM, - - // Department - MODE_KEY_DEPARTMENT = MODE_DEPARTMENT, - RADIO_KEY_COMMAND = RADIO_CHANNEL_COMMAND, - RADIO_KEY_SCIENCE = RADIO_CHANNEL_SCIENCE, - RADIO_KEY_MEDICAL = RADIO_CHANNEL_MEDICAL, - RADIO_KEY_ENGINEERING = RADIO_CHANNEL_ENGINEERING, - RADIO_KEY_SECURITY = RADIO_CHANNEL_SECURITY, - RADIO_KEY_SUPPLY = RADIO_CHANNEL_SUPPLY, - RADIO_KEY_SERVICE = RADIO_CHANNEL_SERVICE, - - // Faction - RADIO_KEY_SYNDICATE = RADIO_CHANNEL_SYNDICATE, - RADIO_KEY_CENTCOM = RADIO_CHANNEL_CENTCOM, - - // Admin - MODE_KEY_ADMIN = MODE_ADMIN, - MODE_KEY_DEADMIN = MODE_DEADMIN, - - // Misc - RADIO_KEY_AI_PRIVATE = RADIO_CHANNEL_AI_PRIVATE, // AI Upload channel - MODE_KEY_VOCALCORDS = MODE_VOCALCORDS, // vocal cords, used by Voice of God - - - //kinda localization -- rastaf0 - //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. - // Location - "ê" = MODE_R_HAND, - "ä" = MODE_L_HAND, - "ø" = MODE_INTERCOM, - - // Department - "ð" = MODE_DEPARTMENT, - "ñ" = RADIO_CHANNEL_COMMAND, - "ò" = RADIO_CHANNEL_SCIENCE, - "ü" = RADIO_CHANNEL_MEDICAL, - "ó" = RADIO_CHANNEL_ENGINEERING, - "û" = RADIO_CHANNEL_SECURITY, - "ã" = RADIO_CHANNEL_SUPPLY, - "ì" = RADIO_CHANNEL_SERVICE, - - // Faction - "å" = RADIO_CHANNEL_SYNDICATE, - "í" = RADIO_CHANNEL_CENTCOM, - - // Admin - "ç" = MODE_ADMIN, - "â" = MODE_ADMIN, - - // Misc - "ù" = RADIO_CHANNEL_AI_PRIVATE, - "÷" = MODE_VOCALCORDS -)) - -/mob/living/proc/Ellipsis(original_msg, chance = 50, keep_words) - if(chance <= 0) - return "..." - if(chance >= 100) - return original_msg - - var/list/words = splittext(original_msg," ") - var/list/new_words = list() - - var/new_msg = "" - - for(var/w in words) - if(prob(chance)) - new_words += "..." - if(!keep_words) - continue - new_words += w - - new_msg = jointext(new_words," ") - - return new_msg - -/mob/living/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - var/static/list/crit_allowed_modes = list(MODE_WHISPER = TRUE, MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) - var/static/list/unconscious_allowed_modes = list(MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) - var/talk_key = get_key(message) - - var/static/list/one_character_prefix = list(MODE_HEADSET = TRUE, MODE_ROBOT = TRUE, MODE_WHISPER = TRUE, MODE_SING = TRUE) - - if(sanitize) - message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) - if(!message || message == "") - return - - var/datum/saymode/saymode = SSradio.saymodes[talk_key] - var/message_mode = get_message_mode(message) - var/original_message = message - var/in_critical = InCritical() - - if(one_character_prefix[message_mode]) - message = copytext(message, 2) - else if(message_mode || saymode) - message = copytext(message, 3) - if(findtext(message, " ", 1, 2)) - message = copytext(message, 2) - - if(message_mode == MODE_ADMIN) - if(client) - client.cmd_admin_say(message) - return - - if(message_mode == MODE_DEADMIN) - if(client) - client.dsay(message) - return - - if(stat == DEAD) - say_dead(original_message) - return - - if(check_emote(original_message) || !can_speak_basic(original_message, ignore_spam)) - return - - if(in_critical) - if(!(crit_allowed_modes[message_mode])) - return - else if(stat == UNCONSCIOUS) - if(!(unconscious_allowed_modes[message_mode])) - return - - // language comma detection. - var/datum/language/message_language = get_message_language(message) - if(message_language) - // No, you cannot speak in xenocommon just because you know the key - if(can_speak_in_language(message_language)) - language = message_language - message = copytext(message, 3) - - // Trim the space if they said ",0 I LOVE LANGUAGES" - if(findtext(message, " ", 1, 2)) - message = copytext(message, 2) - - if(!language) - language = get_default_language() - - // Detection of language needs to be before inherent channels, because - // AIs use inherent channels for the holopad. Most inherent channels - // ignore the language argument however. - - if(saymode && !saymode.handle_message(src, message, language)) - return - - if(!can_speak_vocal(message)) - to_chat(src, "You find yourself unable to speak!") - return - - var/message_range = 7 - - var/succumbed = FALSE - - var/fullcrit = InFullCritical() - if((InCritical() && !fullcrit) || message_mode == MODE_WHISPER) - message_range = 1 - message_mode = MODE_WHISPER - src.log_talk(message, LOG_WHISPER) - if(fullcrit) - var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health) - // If we cut our message short, abruptly end it with a-.. - var/message_len = length(message) - message = copytext(message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]" - message = Ellipsis(message, 10, 1) - message_mode = MODE_WHISPER_CRIT - succumbed = TRUE - else - src.log_talk(message, LOG_SAY, forced_by=forced) - - message = treat_message(message) // unfortunately we still need this - var/sigreturn = SEND_SIGNAL(src, COMSIG_MOB_SAY, args) - if (sigreturn & COMPONENT_UPPERCASE_SPEECH) - message = uppertext(message) - if(!message) - return - - last_words = message - - spans |= speech_span - - if(language) - var/datum/language/L = GLOB.language_datum_instances[language] - spans |= L.spans - - if(message_mode == MODE_SING) - #if DM_VERSION < 513 - var/randomnote = "~" - #else - var/randomnote = pick("\u2669", "\u266A", "\u266B") - #endif - spans |= SPAN_SINGING - message = "[randomnote] [message] [randomnote]" - - var/radio_return = radio(message, message_mode, spans, language) - if(radio_return & ITALICS) - spans |= SPAN_ITALICS - if(radio_return & REDUCE_RANGE) - message_range = 1 - if(radio_return & NOPASS) - return 1 - - //No screams in space, unless you're next to someone. - var/turf/T = get_turf(src) - var/datum/gas_mixture/environment = T.return_air() - var/pressure = (environment)? environment.return_pressure() : 0 - if(pressure < SOUND_MINIMUM_PRESSURE) - message_range = 1 - - if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message - spans |= SPAN_ITALICS - - send_speech(message, message_range, src, bubble_type, spans, language, message_mode) - - if(succumbed) - succumb() - to_chat(src, compose_message(src, language, message, , spans, message_mode)) - - return 1 - -/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(!client) - return - var/deaf_message - var/deaf_type - if(speaker != src) - if(!radio_freq) //These checks have to be seperate, else people talking on the radio will make "You can't hear yourself!" appear when hearing people over the radio while deaf. - deaf_message = "[speaker] [speaker.verb_say] something but you cannot hear [speaker.p_them()]." - deaf_type = 1 - else - deaf_message = "You can't hear yourself!" - deaf_type = 2 // Since you should be able to hear yourself without looking - // Create map text prior to modifying message for goonchat - if (client?.prefs.chat_on_map && stat != UNCONSCIOUS && (client.prefs.see_chat_non_mob || ismob(speaker)) && can_hear()) - create_chat_message(speaker, message_language, raw_message, spans, message_mode) - if (client?.prefs.radiosounds && stat != UNCONSCIOUS && can_hear() && radio_freq) - playsound_local(src,'sound/voice/radio.ogg', 30, 1) - - // Recompose message for AI hrefs, language incomprehension. - message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode) - message = hear_intercept(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - show_message(message, 2, deaf_message, deaf_type) - return message - -/mob/living/proc/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - return message - -/mob/living/send_speech(message, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language=null, message_mode) - var/static/list/eavesdropping_modes = list(MODE_WHISPER = TRUE, MODE_WHISPER_CRIT = TRUE) - var/eavesdrop_range = 0 - if(eavesdropping_modes[message_mode]) - eavesdrop_range = EAVESDROP_EXTRA_RANGE - var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source) - var/list/the_dead = list() - var/list/yellareas //CIT CHANGE - adds the ability for yelling to penetrate walls and echo throughout areas - if(say_test(message) == "2") //CIT CHANGE - ditto - yellareas = get_areas_in_range(message_range*0.5,src) //CIT CHANGE - ditto - for(var/_M in GLOB.player_list) - var/mob/M = _M - if(M.stat != DEAD) //not dead, not important - if(yellareas) //CIT CHANGE - see above. makes yelling penetrate walls - var/area/A = get_area(M) //CIT CHANGE - ditto - if(istype(A) && A.ambientsounds != SPACE && A in yellareas) //CIT CHANGE - ditto - listening |= M //CIT CHANGE - ditto - continue - if(!M.client || !client) //client is so that ghosts don't have to listen to mice - continue - if(get_dist(M, src) > 7 || M.z != z) //they're out of range of normal hearing - if(eavesdropping_modes[message_mode] && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off - continue - if(!(M.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off - continue - listening |= M - the_dead[M] = TRUE - - var/eavesdropping - var/eavesrendered - if(eavesdrop_range) - eavesdropping = stars(message) - eavesrendered = compose_message(src, message_language, eavesdropping, , spans, message_mode) - - var/rendered = compose_message(src, message_language, message, , spans, message_mode) - for(var/_AM in listening) - var/atom/movable/AM = _AM - if(eavesdrop_range && get_dist(source, AM) > message_range && !(the_dead[AM])) - AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mode) - else - AM.Hear(rendered, src, message_language, message, , spans, message_mode) - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message) - - //speech bubble - var/list/speech_bubble_recipients = list() - for(var/mob/M in listening) - if(M.client && !M.client.prefs.chat_on_map) - speech_bubble_recipients.Add(M.client) - var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_recipients, 30) - -/mob/proc/binarycheck() - return FALSE - -/mob/living/can_speak(message) //For use outside of Say() - if(can_speak_basic(message) && can_speak_vocal(message)) - return 1 - -/mob/living/proc/can_speak_basic(message, ignore_spam = FALSE) //Check BEFORE handling of xeno and ling channels - if(client) - if(client.prefs.muted & MUTE_IC) - to_chat(src, "You cannot speak in IC (muted).") - return 0 - if(!ignore_spam && client.handle_spam_prevention(message,MUTE_IC)) - return 0 - - return 1 - -/mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels - if(HAS_TRAIT(src, TRAIT_MUTE)) - return 0 - - if(is_muzzled()) - return 0 - - if(!IsVocal()) - return 0 - - return 1 - -/mob/living/proc/get_key(message) - var/key = copytext(message, 1, 2) - if(key in GLOB.department_radio_prefixes) - return lowertext(copytext(message, 2, 3)) - -/mob/living/proc/get_message_language(message) - if(copytext(message, 1, 2) == ",") - var/key = copytext(message, 2, 3) - for(var/ld in GLOB.all_languages) - var/datum/language/LD = ld - if(initial(LD.key) == key) - return LD - return null - -/mob/living/proc/treat_message(message) - - if(HAS_TRAIT(src, TRAIT_UNINTELLIGIBLE_SPEECH)) - message = unintelligize(message) - - if(derpspeech) - message = derpspeech(message, stuttering) - - if(stuttering) - message = stutter(message) - - if(slurring) - message = slur(message,slurring) - - if(cultslurring) - message = cultslur(message) - - message = capitalize(message) - - return message - -/mob/living/proc/radio(message, message_mode, list/spans, language) - var/obj/item/implant/radio/imp = locate() in implants - if(imp?.radio.on) - if(message_mode == MODE_HEADSET) - imp.radio.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - if(message_mode == MODE_DEPARTMENT || message_mode in GLOB.radiochannels) - imp.radio.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - switch(message_mode) - if(MODE_WHISPER) - return ITALICS - if(MODE_R_HAND) - for(var/obj/item/r_hand in get_held_items_for_side("r", all = TRUE)) - if (r_hand) - return r_hand.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - if(MODE_L_HAND) - for(var/obj/item/l_hand in get_held_items_for_side("l", all = TRUE)) - if (l_hand) - return l_hand.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_INTERCOM) - for (var/obj/item/radio/intercom/I in view(1, null)) - I.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_BINARY) - return ITALICS | REDUCE_RANGE //Does not return 0 since this is only reached by humans, not borgs or AIs. - - return 0 - -/mob/living/say_mod(input, message_mode) - if(message_mode == MODE_WHISPER) - . = verb_whisper - else if(message_mode == MODE_WHISPER_CRIT) - . = "[verb_whisper] in [p_their()] last breath" - else if(stuttering) - . = "stammers" - else if(derpspeech) - . = "gibbers" - else if(message_mode == MODE_SING) - . = verb_sing - else - . = ..() - -/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced) - -/mob/living/get_language_holder(shadow=TRUE) - if(mind && shadow) - // Mind language holders shadow mob holders. - . = mind.get_language_holder() - if(.) - return . - - . = ..() +GLOBAL_LIST_INIT(department_radio_prefixes, list(":", ".")) + +GLOBAL_LIST_INIT(department_radio_keys, list( + // Location + MODE_KEY_R_HAND = MODE_R_HAND, + MODE_KEY_L_HAND = MODE_L_HAND, + MODE_KEY_INTERCOM = MODE_INTERCOM, + + // Department + MODE_KEY_DEPARTMENT = MODE_DEPARTMENT, + RADIO_KEY_COMMAND = RADIO_CHANNEL_COMMAND, + RADIO_KEY_SCIENCE = RADIO_CHANNEL_SCIENCE, + RADIO_KEY_MEDICAL = RADIO_CHANNEL_MEDICAL, + RADIO_KEY_ENGINEERING = RADIO_CHANNEL_ENGINEERING, + RADIO_KEY_SECURITY = RADIO_CHANNEL_SECURITY, + RADIO_KEY_SUPPLY = RADIO_CHANNEL_SUPPLY, + RADIO_KEY_SERVICE = RADIO_CHANNEL_SERVICE, + + // Faction + RADIO_KEY_SYNDICATE = RADIO_CHANNEL_SYNDICATE, + RADIO_KEY_CENTCOM = RADIO_CHANNEL_CENTCOM, + + // Admin + MODE_KEY_ADMIN = MODE_ADMIN, + MODE_KEY_DEADMIN = MODE_DEADMIN, + + // Misc + RADIO_KEY_AI_PRIVATE = RADIO_CHANNEL_AI_PRIVATE, // AI Upload channel + MODE_KEY_VOCALCORDS = MODE_VOCALCORDS, // vocal cords, used by Voice of God + + + //kinda localization -- rastaf0 + //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. + // Location + "ê" = MODE_R_HAND, + "ä" = MODE_L_HAND, + "ø" = MODE_INTERCOM, + + // Department + "ð" = MODE_DEPARTMENT, + "ñ" = RADIO_CHANNEL_COMMAND, + "ò" = RADIO_CHANNEL_SCIENCE, + "ü" = RADIO_CHANNEL_MEDICAL, + "ó" = RADIO_CHANNEL_ENGINEERING, + "û" = RADIO_CHANNEL_SECURITY, + "ã" = RADIO_CHANNEL_SUPPLY, + "ì" = RADIO_CHANNEL_SERVICE, + + // Faction + "å" = RADIO_CHANNEL_SYNDICATE, + "í" = RADIO_CHANNEL_CENTCOM, + + // Admin + "ç" = MODE_ADMIN, + "â" = MODE_ADMIN, + + // Misc + "ù" = RADIO_CHANNEL_AI_PRIVATE, + "÷" = MODE_VOCALCORDS +)) + +/mob/living/proc/Ellipsis(original_msg, chance = 50, keep_words) + if(chance <= 0) + return "..." + if(chance >= 100) + return original_msg + + var/list/words = splittext(original_msg," ") + var/list/new_words = list() + + var/new_msg = "" + + for(var/w in words) + if(prob(chance)) + new_words += "..." + if(!keep_words) + continue + new_words += w + + new_msg = jointext(new_words," ") + + return new_msg + +/mob/living/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + var/static/list/crit_allowed_modes = list(MODE_WHISPER = TRUE, MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) + var/static/list/unconscious_allowed_modes = list(MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) + var/talk_key = get_key(message) + + var/static/list/one_character_prefix = list(MODE_HEADSET = TRUE, MODE_ROBOT = TRUE, MODE_WHISPER = TRUE, MODE_SING = TRUE) + + if(sanitize) + message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) + if(!message || message == "") + return + + var/datum/saymode/saymode = SSradio.saymodes[talk_key] + var/message_mode = get_message_mode(message) + var/original_message = message + var/in_critical = InCritical() + + if(one_character_prefix[message_mode]) + message = copytext(message, 2) + else if(message_mode || saymode) + message = copytext(message, 3) + if(findtext(message, " ", 1, 2)) + message = copytext(message, 2) + + if(message_mode == MODE_ADMIN) + if(client) + client.cmd_admin_say(message) + return + + if(message_mode == MODE_DEADMIN) + if(client) + client.dsay(message) + return + + if(stat == DEAD) + say_dead(original_message) + return + + if(check_emote(original_message) || !can_speak_basic(original_message, ignore_spam)) + return + + if(in_critical) + if(!(crit_allowed_modes[message_mode])) + return + else if(stat == UNCONSCIOUS) + if(!(unconscious_allowed_modes[message_mode])) + return + + // language comma detection. + var/datum/language/message_language = get_message_language(message) + if(message_language) + // No, you cannot speak in xenocommon just because you know the key + if(can_speak_in_language(message_language)) + language = message_language + message = copytext(message, 3) + + // Trim the space if they said ",0 I LOVE LANGUAGES" + if(findtext(message, " ", 1, 2)) + message = copytext(message, 2) + + if(!language) + language = get_default_language() + + // Detection of language needs to be before inherent channels, because + // AIs use inherent channels for the holopad. Most inherent channels + // ignore the language argument however. + + if(saymode && !saymode.handle_message(src, message, language)) + return + + if(!can_speak_vocal(message)) + to_chat(src, "You find yourself unable to speak!") + return + + var/message_range = 7 + + var/succumbed = FALSE + + var/fullcrit = InFullCritical() + if((InCritical() && !fullcrit) || message_mode == MODE_WHISPER) + message_range = 1 + message_mode = MODE_WHISPER + src.log_talk(message, LOG_WHISPER) + if(fullcrit) + var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health) + // If we cut our message short, abruptly end it with a-.. + var/message_len = length(message) + message = copytext(message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]" + message = Ellipsis(message, 10, 1) + message_mode = MODE_WHISPER_CRIT + succumbed = TRUE + else + src.log_talk(message, LOG_SAY, forced_by=forced) + + message = treat_message(message) // unfortunately we still need this + var/sigreturn = SEND_SIGNAL(src, COMSIG_MOB_SAY, args) + if (sigreturn & COMPONENT_UPPERCASE_SPEECH) + message = uppertext(message) + if(!message) + return + + last_words = message + + spans |= speech_span + + if(language) + var/datum/language/L = GLOB.language_datum_instances[language] + spans |= L.spans + + if(message_mode == MODE_SING) + #if DM_VERSION < 513 + var/randomnote = "~" + #else + var/randomnote = pick("\u2669", "\u266A", "\u266B") + #endif + spans |= SPAN_SINGING + message = "[randomnote] [message] [randomnote]" + + var/radio_return = radio(message, message_mode, spans, language) + if(radio_return & ITALICS) + spans |= SPAN_ITALICS + if(radio_return & REDUCE_RANGE) + message_range = 1 + if(radio_return & NOPASS) + return 1 + + //No screams in space, unless you're next to someone. + var/turf/T = get_turf(src) + var/datum/gas_mixture/environment = T.return_air() + var/pressure = (environment)? environment.return_pressure() : 0 + if(pressure < SOUND_MINIMUM_PRESSURE) + message_range = 1 + + if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message + spans |= SPAN_ITALICS + + send_speech(message, message_range, src, bubble_type, spans, language, message_mode) + + if(succumbed) + succumb() + to_chat(src, compose_message(src, language, message, , spans, message_mode)) + + return 1 + +/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(!client) + return + var/deaf_message + var/deaf_type + if(speaker != src) + if(!radio_freq) //These checks have to be seperate, else people talking on the radio will make "You can't hear yourself!" appear when hearing people over the radio while deaf. + deaf_message = "[speaker] [speaker.verb_say] something but you cannot hear [speaker.p_them()]." + deaf_type = 1 + else + deaf_message = "You can't hear yourself!" + deaf_type = 2 // Since you should be able to hear yourself without looking + // Create map text prior to modifying message for goonchat + if (client?.prefs.chat_on_map && stat != UNCONSCIOUS && (client.prefs.see_chat_non_mob || ismob(speaker)) && can_hear()) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) + if (client?.prefs.radiosounds && stat != UNCONSCIOUS && can_hear() && radio_freq) + playsound_local(src,'sound/voice/radio.ogg', 30, 1) + + // Recompose message for AI hrefs, language incomprehension. + message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode) + message = hear_intercept(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + show_message(message, MSG_AUDIBLE, deaf_message, deaf_type) + return message + +/mob/living/proc/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) + return message + +/mob/living/send_speech(message, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language=null, message_mode) + var/static/list/eavesdropping_modes = list(MODE_WHISPER = TRUE, MODE_WHISPER_CRIT = TRUE) + var/eavesdrop_range = 0 + if(eavesdropping_modes[message_mode]) + eavesdrop_range = EAVESDROP_EXTRA_RANGE + var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source) + var/list/the_dead = list() + var/list/yellareas //CIT CHANGE - adds the ability for yelling to penetrate walls and echo throughout areas + if(say_test(message) == "2") //CIT CHANGE - ditto + yellareas = get_areas_in_range(message_range*0.5,src) //CIT CHANGE - ditto + for(var/_M in GLOB.player_list) + var/mob/M = _M + if(M.stat != DEAD) //not dead, not important + if(yellareas) //CIT CHANGE - see above. makes yelling penetrate walls + var/area/A = get_area(M) //CIT CHANGE - ditto + if(istype(A) && A.ambientsounds != SPACE && A in yellareas) //CIT CHANGE - ditto + listening |= M //CIT CHANGE - ditto + continue + if(!M.client || !client) //client is so that ghosts don't have to listen to mice + continue + if(get_dist(M, src) > 7 || M.z != z) //they're out of range of normal hearing + if(eavesdropping_modes[message_mode] && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off + continue + if(!(M.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off + continue + listening |= M + the_dead[M] = TRUE + + var/eavesdropping + var/eavesrendered + if(eavesdrop_range) + eavesdropping = stars(message) + eavesrendered = compose_message(src, message_language, eavesdropping, , spans, message_mode) + + var/rendered = compose_message(src, message_language, message, , spans, message_mode) + for(var/_AM in listening) + var/atom/movable/AM = _AM + if(eavesdrop_range && get_dist(source, AM) > message_range && !(the_dead[AM])) + AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mode) + else + AM.Hear(rendered, src, message_language, message, , spans, message_mode) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message) + + //speech bubble + var/list/speech_bubble_recipients = list() + for(var/mob/M in listening) + if(M.client && !M.client.prefs.chat_on_map) + speech_bubble_recipients.Add(M.client) + var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_recipients, 30) + +/mob/proc/binarycheck() + return FALSE + +/mob/living/can_speak(message) //For use outside of Say() + if(can_speak_basic(message) && can_speak_vocal(message)) + return 1 + +/mob/living/proc/can_speak_basic(message, ignore_spam = FALSE) //Check BEFORE handling of xeno and ling channels + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, "You cannot speak in IC (muted).") + return 0 + if(!ignore_spam && client.handle_spam_prevention(message,MUTE_IC)) + return 0 + + return 1 + +/mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels + if(HAS_TRAIT(src, TRAIT_MUTE)) + return 0 + + if(is_muzzled()) + return 0 + + if(!IsVocal()) + return 0 + + return 1 + +/mob/living/proc/get_key(message) + var/key = copytext(message, 1, 2) + if(key in GLOB.department_radio_prefixes) + return lowertext(copytext(message, 2, 3)) + +/mob/living/proc/get_message_language(message) + if(copytext(message, 1, 2) == ",") + var/key = copytext(message, 2, 3) + for(var/ld in GLOB.all_languages) + var/datum/language/LD = ld + if(initial(LD.key) == key) + return LD + return null + +/mob/living/proc/treat_message(message) + + if(HAS_TRAIT(src, TRAIT_UNINTELLIGIBLE_SPEECH)) + message = unintelligize(message) + + if(derpspeech) + message = derpspeech(message, stuttering) + + if(stuttering) + message = stutter(message) + + if(slurring) + message = slur(message,slurring) + + if(cultslurring) + message = cultslur(message) + + message = capitalize(message) + + return message + +/mob/living/proc/radio(message, message_mode, list/spans, language) + var/obj/item/implant/radio/imp = locate() in implants + if(imp?.radio.on) + if(message_mode == MODE_HEADSET) + imp.radio.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + if(message_mode == MODE_DEPARTMENT || message_mode in GLOB.radiochannels) + imp.radio.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + switch(message_mode) + if(MODE_WHISPER) + return ITALICS + if(MODE_R_HAND) + for(var/obj/item/r_hand in get_held_items_for_side("r", all = TRUE)) + if (r_hand) + return r_hand.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + if(MODE_L_HAND) + for(var/obj/item/l_hand in get_held_items_for_side("l", all = TRUE)) + if (l_hand) + return l_hand.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_INTERCOM) + for (var/obj/item/radio/intercom/I in view(1, null)) + I.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_BINARY) + return ITALICS | REDUCE_RANGE //Does not return 0 since this is only reached by humans, not borgs or AIs. + + return 0 + +/mob/living/say_mod(input, message_mode) + if(message_mode == MODE_WHISPER) + . = verb_whisper + else if(message_mode == MODE_WHISPER_CRIT) + . = "[verb_whisper] in [p_their()] last breath" + else if(stuttering) + . = "stammers" + else if(derpspeech) + . = "gibbers" + else if(message_mode == MODE_SING) + . = verb_sing + else + . = ..() + +/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced) + +/mob/living/get_language_holder(shadow=TRUE) + if(mind && shadow) + // Mind language holders shadow mob holders. + . = mind.get_language_holder() + if(.) + return . + + . = ..() diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 529c8a68..efc714f2 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1,1001 +1,1001 @@ -#define CALL_BOT_COOLDOWN 900 - -//Not sure why this is necessary... -/proc/AutoUpdateAI(obj/subject) - var/is_in_use = 0 - if (subject!=null) - for(var/A in GLOB.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_OVERPOWERING - density = TRUE - canmove = FALSE - status_flags = CANSTUN|CANPUSH - a_intent = INTENT_HARM //so we always get pushed instead of trying to swap - sight = SEE_TURFS | SEE_MOBS | SEE_OBJS - see_in_dark = 8 - hud_type = /datum/hud/ai - med_hud = DATA_HUD_MEDICAL_BASIC - sec_hud = DATA_HUD_SECURITY_BASIC - d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED - mob_size = MOB_SIZE_LARGE - var/list/network = list("ss13") - var/obj/machinery/camera/current - var/list/connected_robots = list() - var/aiRestorePowerRoutine = 0 - var/requires_power = POWER_REQ_ALL - var/can_be_carded = TRUE - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=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/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not. - radiomod = ";" //AIs will, by default, state their laws on the internal radio. - var/obj/item/pda/ai/aiPDA - var/obj/item/multitool/aiMulti - var/mob/living/simple_animal/bot/Bot - var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. - var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N - - //MALFUNCTION - var/datum/module_picker/malf_picker - var/list/datum/AI_Module/current_modules = list() - var/can_dominate_mechs = FALSE - var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead - - var/control_disabled = FALSE // Set to 1 to stop AI from interacting via Click() - var/malfhacking = FALSE // 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 - var/explosive = FALSE //does the AI explode when it dies? - - var/mob/living/silicon/ai/parent - var/camera_light_on = FALSE - var/list/obj/machinery/camera/lit_cameras = list() - - var/datum/trackable/track = new - - var/last_paper_seen = null - var/can_shunt = TRUE - var/last_announcement = "" // For AI VOX, if enabled - var/turf/waypoint //Holds the turf of the currently selected waypoint. - var/waypoint_mode = FALSE //Waypoint mode is for selecting a turf via clicking. - var/call_bot_cooldown = 0 //time of next call bot command - var/apc_override = FALSE //hack for letting the AI use its APC even when visionless - var/nuking = FALSE - var/obj/machinery/doomsday_device/doomsday_device - - var/mob/camera/aiEye/eyeobj - var/sprint = 10 - var/cooldown = 0 - var/acceleration = 1 - - var/obj/structure/AIcore/deactivated/linked_core //For exosuit control - var/mob/living/silicon/robot/deployed_shell = null //For shell control - var/datum/action/innate/deploy_shell/deploy_action = new - var/datum/action/innate/deploy_last_shell/redeploy_action = new - var/chnotify = 0 - - - 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 - var/display_icon_override - -/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai) - . = ..() - if(!target_ai) //If there is no player/brain inside. - new/obj/structure/AIcore/deactivated(loc) //New empty terminal. - return INITIALIZE_HINT_QDEL //Delete AI. - - ADD_TRAIT(src, TRAIT_NO_TELEPORT, src) - if(L && istype(L, /datum/ai_laws)) - laws = L - laws.associate(src) - else - make_laws() - - if(target_ai.mind) - target_ai.mind.transfer_to(src) - if(mind.special_role) - mind.store_memory("As an AI, you must obey your silicon laws above all else. Your objectives will consider you to be dead.") - to_chat(src, "You have been installed as an AI! ") - to_chat(src, "You must obey your silicon laws above all else. Your objectives will consider you to be dead.") - - 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.") - to_chat(src, "For department channels, use the following say commands:") - to_chat(src, ":o - AI Private, :c - Command, :s - Security, :e - Engineering, :u - Supply, :v - Service, :m - Medical, :n - Science.") - show_laws() - to_chat(src, "These laws may be changed by other players, or by you being the traitor.") - - job = "AI" - - create_eye() - apply_pref_name("ai") - - set_core_display_icon() - - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"default")) - - spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - - verbs += /mob/living/silicon/ai/proc/show_laws_verb - - aiPDA = new/obj/item/pda/ai(src) - aiPDA.owner = name - aiPDA.ownjob = "AI" - aiPDA.name = name + " (" + aiPDA.ownjob + ")" - - aiMulti = new(src) - radio = new /obj/item/radio/headset/ai(src) - aicamera = new/obj/item/camera/siliconcam/ai_camera(src) - - deploy_action.Grant(src) - - if(isturf(loc)) - verbs.Add(/mob/living/silicon/ai/proc/ai_network_change, \ - /mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \ - /mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \ - /mob/living/silicon/ai/proc/set_automatic_say_channel) - - GLOB.ai_list += src - GLOB.shuttle_caller_list += src - - builtInCamera = new (src) - builtInCamera.network = list("ss13") - - -/mob/living/silicon/ai/Destroy() - GLOB.ai_list -= src - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - qdel(eyeobj) // No AI, no Eye - malfhack = null - - . = ..() - -/mob/living/silicon/ai/IgniteMob() - fire_stacks = 0 - . = ..() - -/mob/living/silicon/ai/proc/set_core_display_icon(input, client/C) - if(client && !C) - C = client - if(!input && !C?.prefs?.preferred_ai_core_display) - icon_state = initial(icon_state) - else - var/preferred_icon = input ? input : C.prefs.preferred_ai_core_display - icon_state = resolve_ai_icon(preferred_icon) - -/mob/living/silicon/ai/verb/pick_icon() - set category = "AI Commands" - set name = "Set AI Core Display" - if(incapacitated()) - return - var/list/iconstates = GLOB.ai_core_display_screens - for(var/option in iconstates) - if(option == "Random") - iconstates[option] = image(icon = src.icon, icon_state = "ai-random") - continue - iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option)) - - view_core() - var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42) - - if(!ai_core_icon || incapacitated()) - return - display_icon_override = ai_core_icon - set_core_display_icon(ai_core_icon) - -/mob/living/silicon/ai/Stat() - ..() - if(statpanel("Status")) - if(!stat) - stat(null, text("System integrity: [(health+100)/2]%")) - stat(null, text("Connected cyborgs: [connected_robots.len]")) - for(var/mob/living/silicon/robot/R in connected_robots) - var/robot_status = "Nominal" - if(R.shell) - robot_status = "AI SHELL" - else 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! - stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ - Module: [R.designation] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) - stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells - else - stat(null, text("Systems nonfunctional")) - -/mob/living/silicon/ai/proc/ai_alerts() - var/dat = "Current Station Alerts\n" - dat += "Close

" - for (var/cat in alarms) - dat += text("[]
\n", cat) - var/list/L = alarms[cat] - if (L.len) - for (var/alarm in L) - var/list/alm = L[alarm] - var/area/A = alm[1] - var/C = alm[2] - var/list/sources = alm[3] - dat += "" - if (C && istype(C, /list)) - var/dat2 = "" - for (var/obj/machinery/camera/I in C) - dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) - dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera") - else if (C && istype(C, /obj/machinery/camera)) - var/obj/machinery/camera/Ctmp = C - dat += text("-- [] ([])", A.name, Ctmp.c_tag) - else - dat += text("-- [] (No Camera)", A.name) - if (sources.len > 1) - dat += text("- [] sources", sources.len) - dat += "
\n" - else - dat += "-- All Systems Nominal
\n" - dat += "
\n" - - viewalerts = 1 - src << browse(dat, "window=aialerts&can_close=0") - -/mob/living/silicon/ai/proc/ai_roster() - var/dat = "Crew RosterCrew Roster:

" - - dat += GLOB.data_core.get_manifest() - dat += "" - - src << browse(dat, "window=airoster") - onclose(src, "airoster") - -/mob/living/silicon/ai/proc/ai_call_shuttle() - if(control_disabled) - to_chat(usr, "Wireless control is disabled!") - return - - var/reason = input(src, "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call") as null|text - - if(incapacitated()) - return - - if(trim(reason)) - SSshuttle.requestEvac(src, reason) - - // hack to display shuttle timer - if(!EMERGENCY_IDLE_OR_RECALLED) - var/obj/machinery/computer/communications/C = locate() in GLOB.machines - if(C) - C.post_status("shuttle") - -/mob/living/silicon/ai/can_interact_with(atom/A) - . = ..() - var/turf/ai = get_turf(src) - var/turf/target = get_turf(A) - if (.) - return - - if(!target) - return - - if ((ai.z != target.z) && !is_station_level(ai.z)) - return FALSE - - if (istype(loc, /obj/item/aicard)) - var/turf/T0 = get_turf(src) - var/turf/T1 = get_turf(A) - if (!T0 || ! T1) - return FALSE - return ISINRANGE(T1.x, T0.x - interaction_range, T0.x + interaction_range) && ISINRANGE(T1.y, T0.y - interaction_range, T0.y + interaction_range) - else - return GLOB.cameranet.checkTurfVis(get_turf(A)) - -/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(incapacitated()) - return - var/is_anchored = FALSE - if(move_resist == MOVE_FORCE_OVERPOWERING) - move_resist = MOVE_FORCE_NORMAL - else - is_anchored = TRUE - move_resist = MOVE_FORCE_OVERPOWERING - REMOVE_TRAIT(src, TRAIT_NO_TELEPORT, src) - ADD_TRAIT(src, TRAIT_NO_TELEPORT, src) - - to_chat(src, "You are now [is_anchored ? "" : "un"]anchored.") - // the message in the [] will change depending whether or not the AI is anchored - -/mob/living/silicon/ai/update_canmove() //If the AI dies, mobs won't go through it anymore - return 0 - -/mob/living/silicon/ai/proc/ai_cancel_call() - set category = "Malfunction" - if(control_disabled) - to_chat(src, "Wireless control is disabled!") - return - SSshuttle.cancelEvac(src) - -/mob/living/silicon/ai/restrained(ignore_grab) - . = 0 - -/mob/living/silicon/ai/Topic(href, href_list) - if(usr != src || incapacitated()) - 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 GLOB.cameranet.cameras - if (href_list["showalerts"]) - ai_alerts() -#ifdef AI_VOX - if(href_list["say_word"]) - play_vox_word(href_list["say_word"], null, src) - return -#endif - 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/holopad/H = locate(href_list["jumptoholopad"]) - if(H) - H.attack_ai(src) //may as well recycle - else - to_chat(src, "Unable to locate the holopad.") - if(href_list["track"]) - var/string = href_list["track"] - trackable_mobs() - var/list/trackeable = list() - trackeable += track.humans + track.others - var/list/target = list() - for(var/I in trackeable) - var/mob/M = trackeable[I] - if(M.name == string) - target += M - if(name == string) - target += src - if(target.len) - ai_actual_track(pick(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. - if(call_bot_cooldown > world.time) - to_chat(src, "Error: Your last call bot command is still processing, please wait for the bot to finish calculating a route.") - return - Bot = locate(href_list["callbot"]) in GLOB.alive_mob_list - if(!Bot || Bot.remote_disabled || src.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.alive_mob_list - if(!Bot || Bot.remote_disabled || src.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(controlled_mech) - to_chat(src, "You are already loaded into an onboard computer!") - return - if(!GLOB.cameranet.checkCameraVis(M)) - to_chat(src, "Exosuit is no longer near active cameras.") - 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. - - -/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C) - if(QDELETED(C)) - return FALSE - - if(!tracking) - cameraFollow = null - - if(QDELETED(eyeobj)) - view_core() - return - // ok, we're alive, camera is good and in our network... - eyeobj.setLoc(get_turf(C)) - return TRUE - -/mob/living/silicon/ai/proc/botcall() - set category = "AI Commands" - set name = "Access Robot Control" - set desc = "Wirelessly control various automatic robots." - if(incapacitated()) - return - - if(control_disabled) - to_chat(src, "Wireless control is disabled.") - return - var/turf/ai_current_turf = get_turf(src) - var/ai_Zlevel = ai_current_turf.z - var/d - d += "Query network status
" - d += "" - - for (Bot in GLOB.alive_mob_list) - if(Bot.z == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected! - var/bot_mode = Bot.get_mode() - 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(GLOB.cameranet && GLOB.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 - to_chat(src, "Sending command to bot...") - call_bot_cooldown = world.time + CALL_BOT_COOLDOWN - Bot.call_bot(src, waypoint) - call_bot_cooldown = 0 - - -/mob/living/silicon/ai/triggerAlarm(class, area/A, O, obj/alarmsource) - if(alarmsource.z != z) - return - var/list/L = alarms[class] - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/sources = alarm[3] - if (!(alarmsource in sources)) - sources += alarmsource - return 1 - var/obj/machinery/camera/C = null - var/list/CL = null - if (O && istype(O, /list)) - CL = O - if (CL.len == 1) - C = CL[1] - else if (O && istype(O, /obj/machinery/camera)) - C = O - L[A.name] = list(A, (C) ? C : O, list(alarmsource)) - if (O) - if (C && C.can_use()) - queueAlarm("--- [class] alarm detected in [A.name]! ([C.c_tag])", class) - else if (CL && CL.len) - var/foo = 0 - var/dat2 = "" - for (var/obj/machinery/camera/I in CL) - dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... - foo = 1 - queueAlarm(text ("--- [] alarm detected in []! ([])", class, A.name, dat2), class) - else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) - else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) - if (viewalerts) ai_alerts() - return 1 - -/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - if (cleared) - queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) - if (viewalerts) ai_alerts() - return !cleared - -//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() - cameraFollow = null - var/cameralist[0] - - if(incapacitated()) - return - - var/mob/living/silicon/ai/U = usr - - for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) - var/list/tempnetwork = C.network - if(!(is_station_level(C.z) || is_mining_level(C.z) || ("ss13" in tempnetwork))) - continue - if(!C.can_use()) - continue - - tempnetwork.Remove("rd", "toxins", "prison") - if(tempnetwork.len) - for(var/i in C.network) - cameralist[i] = i - var/old_network = network - network = input(U, "Which network would you like to view?") as null|anything in cameralist - - 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 GLOB.cameranet.cameras) - if(!C.can_use()) - continue - if(network in C.network) - U.eyeobj.setLoc(get_turf(C)) - break - to_chat(src, "Switched to the \"[uppertext(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(incapacitated()) - return - var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Thinking", "Friend Computer", "Dorfy", "Blue Glow", "Red Glow") - var/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions - for (var/each in GLOB.ai_status_displays) //change status of displays - var/obj/machinery/status_display/ai/M = each - M.emotion = emote - M.update() - if (emote == "Friend Computer") - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = "friendcomputer")) - frequency.post_signal(src, status_signal) - 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(incapacitated()) - return - 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/list/personnel_list = list() - - for(var/datum/data/record/t in GLOB.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/list/icon_list = list( - "bear" = 'icons/mob/animal.dmi', - "carp" = 'icons/mob/animal.dmi', - "chicken" = 'icons/mob/animal.dmi', - "corgi" = 'icons/mob/pets.dmi', - "cow" = 'icons/mob/animal.dmi', - "crab" = 'icons/mob/animal.dmi', - "fox" = 'icons/mob/pets.dmi', - "goat" = 'icons/mob/animal.dmi', - "cat" = 'icons/mob/pets.dmi', - "cat2" = 'icons/mob/pets.dmi', - "poly" = 'icons/mob/animal.dmi', - "pug" = 'icons/mob/pets.dmi', - "spider" = 'icons/mob/animal.dmi' - ) - - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("poly") - holo_icon = getHologramIcon(icon(icon_list[input],"parrot_fly")) - if("chicken") - holo_icon = getHologramIcon(icon(icon_list[input],"chicken_brown")) - if("spider") - holo_icon = getHologramIcon(icon(icon_list[input],"guard")) - else - holo_icon = getHologramIcon(icon(icon_list[input], input)) - else - var/list/icon_list = list( - "default" = 'icons/mob/ai.dmi', - "floating face" = 'icons/mob/ai.dmi', - "xeno queen" = 'icons/mob/alien.dmi', - "horror" = 'icons/mob/ai.dmi' - ) - - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("xeno queen") - holo_icon = getHologramIcon(icon(icon_list[input],"alienq")) - else - holo_icon = getHologramIcon(icon(icon_list[input], input)) - return - -/mob/living/silicon/ai/proc/corereturn() - set category = "Malfunction" - set name = "Return to Main Core" - - var/obj/machinery/power/apc/apc = src.loc - if(!istype(apc)) - to_chat(src, "You are already in your Main Core.") - return - apc.malfvacate() - -/mob/living/silicon/ai/proc/toggle_camera_light() - 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.") - -//AI_CAMERA_LUMINOSITY - -/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 || !C.internal_light) - 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/proc/control_integrated_radio() - set name = "Transceiver Settings" - set desc = "Allows you to change settings of your radio." - set category = "AI Commands" - - if(incapacitated()) - return - - to_chat(src, "Accessing Subspace Transceiver control...") - if (radio) - radio.interact(src) - -/mob/living/silicon/ai/proc/set_syndie_radio() - if(radio) - radio.make_syndie() - -/mob/living/silicon/ai/proc/set_automatic_say_channel() - set name = "Set Auto Announce Mode" - set desc = "Modify the default radio setting for your automatic announcements." - set category = "AI Commands" - - if(incapacitated()) - return - set_autosay() - -/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(!can_be_carded) - to_chat(user, "Transfer failed.") - return - disconnect_shell() //If the AI is controlling a borg, force the player back to core! - if(!mind) - to_chat(user, "No intelligence patterns detected." ) - return - ShutOffDoomsdayDevice() - new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. - ai_restore_power()//So the AI initially has power. - control_disabled = 1//Can't control things remotely if you're stuck in a card! - radio_enabled = 0 //No talking on the built-in radio for you either! - forceMove(card) - card.AI = src - 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 0 - -/mob/living/silicon/ai/incapacitated() - if(aiRestorePowerRoutine) - return TRUE - return ..() - -/mob/living/silicon/ai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) - if(control_disabled || incapacitated()) - to_chat(src, "You can't do that right now!") - return FALSE - if(be_close && !in_range(M, src)) - to_chat(src, "You are too far away!") - return FALSE - return can_see(M) //stop AIs from leaving windows open and using then after they lose vision - -/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 - return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(get_turf_pixel(A))) || 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(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - var/treated_message = lang_treat(speaker, message_language, raw_message, spans, message_mode) - var/start = "Relayed Speech: " - var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]" - var/hrefpart = "" - var/jobpart - - if (iscarbon(speaker)) - var/mob/living/carbon/S = speaker - if(S.job) - jobpart = "[S.job]" - else - jobpart = "Unknown" - - var/rendered = "[start][hrefpart][namepart] ([jobpart])[treated_message]" - if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) - create_chat_message(speaker, message_language, raw_message, spans, message_mode) - - show_message(rendered, 2) - -/mob/living/silicon/ai/fully_replace_character_name(oldname,newname) - ..() - if(oldname != real_name) - if(eyeobj) - eyeobj.name = "[newname] (AI Eye)" - - // Notify Cyborgs - for(var/mob/living/silicon/robot/Slave in connected_robots) - Slave.show_laws() - -/mob/living/silicon/ai/replace_identification_name(oldname,newname) - if(aiPDA) - aiPDA.owner = newname - aiPDA.name = newname + " (" + aiPDA.ownjob + ")" - - -/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/reset_perspective(atom/A) - if(camera_light_on) - light_cameras() - if(istype(A, /obj/machinery/camera)) - current = A - if(client) - if(ismovableatom(A)) - if(A != GLOB.ai_camera_room_landmark) - end_multicam() - client.perspective = EYE_PERSPECTIVE - client.eye = A - else - end_multicam() - if(isturf(loc)) - if(eyeobj) - client.eye = eyeobj - client.perspective = EYE_PERSPECTIVE - else - client.eye = client.mob - client.perspective = MOB_PERSPECTIVE - else - client.perspective = EYE_PERSPECTIVE - client.eye = loc - update_sight() - if(client.eye != src) - var/atom/AT = client.eye - AT.get_remote_view_fullscreens(src) - else - clear_fullscreen("remote_view", 0) - -/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0) - . = ..() - if(.) //successfully ressuscitated from death - set_core_display_icon(display_icon_override) - -/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. \The [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 - apc.coverlocked = TRUE - - playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1) - to_chat(src, "Hack complete. \The [apc] is now under your exclusive control.") - apc.update_icon() - -/mob/living/silicon/ai/verb/deploy_to_shell(var/mob/living/silicon/robot/target) - set category = "AI Commands" - set name = "Deploy to Shell" - - if(incapacitated()) - return - if(control_disabled) - to_chat(src, "Wireless networking module is offline.") - return - - var/list/possible = list() - - for(var/borgie in GLOB.available_ai_shells) - var/mob/living/silicon/robot/R = borgie - if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai ||(R.connected_ai == src))) - possible += R - - if(!LAZYLEN(possible)) - to_chat(src, "No usable AI shell beacons detected.") - - if(!target || !(target in possible)) //If the AI is looking for a new shell, or its pre-selected shell is no longer valid - target = input(src, "Which body to control?") as null|anything in possible - - if (!target || target.stat == DEAD || target.deployed || !(!target.connected_ai ||(target.connected_ai == src))) - return - - else if(mind) - soullink(/datum/soullink/sharedbody, src, target) - deployed_shell = target - target.deploy_init(src) - mind.transfer_to(target) - diag_hud_set_deployed() - -/datum/action/innate/deploy_shell - name = "Deploy to AI Shell" - desc = "Wirelessly control a specialized cyborg shell." - icon_icon = 'icons/mob/actions/actions_AI.dmi' - button_icon_state = "ai_shell" - -/datum/action/innate/deploy_shell/Trigger() - var/mob/living/silicon/ai/AI = owner - if(!AI) - return - AI.deploy_to_shell() - -/datum/action/innate/deploy_last_shell - name = "Reconnect to shell" - desc = "Reconnect to the most recently used AI shell." - icon_icon = 'icons/mob/actions/actions_AI.dmi' - button_icon_state = "ai_last_shell" - var/mob/living/silicon/robot/last_used_shell - -/datum/action/innate/deploy_last_shell/Trigger() - if(!owner) - return - if(last_used_shell) - var/mob/living/silicon/ai/AI = owner - AI.deploy_to_shell(last_used_shell) - else - Remove(owner) //If the last shell is blown, destroy it. - -/mob/living/silicon/ai/proc/disconnect_shell() - if(deployed_shell) //Forcibly call back AI in event of things such as damage, EMP or power loss. - to_chat(src, "Your remote connection has been reset!") - deployed_shell.undeploy() - diag_hud_set_deployed() - -/mob/living/silicon/ai/resist() - return - -/mob/living/silicon/ai/spawned/Initialize(mapload, datum/ai_laws/L, mob/target_ai) - . = ..() - if(!target_ai) - target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct - -/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye) - GLOB.cameranet.visibility(moved_eye, client, all_eyes, USE_STATIC_OPAQUE) - -/mob/living/silicon/ai/forceMove(atom/destination) - . = ..() - if(.) - end_multicam() +#define CALL_BOT_COOLDOWN 900 + +//Not sure why this is necessary... +/proc/AutoUpdateAI(obj/subject) + var/is_in_use = 0 + if (subject!=null) + for(var/A in GLOB.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_OVERPOWERING + density = TRUE + canmove = FALSE + status_flags = CANSTUN|CANPUSH + a_intent = INTENT_HARM //so we always get pushed instead of trying to swap + sight = SEE_TURFS | SEE_MOBS | SEE_OBJS + see_in_dark = 8 + hud_type = /datum/hud/ai + med_hud = DATA_HUD_MEDICAL_BASIC + sec_hud = DATA_HUD_SECURITY_BASIC + d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED + mob_size = MOB_SIZE_LARGE + var/list/network = list("ss13") + var/obj/machinery/camera/current + var/list/connected_robots = list() + var/aiRestorePowerRoutine = 0 + var/requires_power = POWER_REQ_ALL + var/can_be_carded = TRUE + var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=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/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not. + radiomod = ";" //AIs will, by default, state their laws on the internal radio. + var/obj/item/pda/ai/aiPDA + var/obj/item/multitool/aiMulti + var/mob/living/simple_animal/bot/Bot + var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. + var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N + + //MALFUNCTION + var/datum/module_picker/malf_picker + var/list/datum/AI_Module/current_modules = list() + var/can_dominate_mechs = FALSE + var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead + + var/control_disabled = FALSE // Set to 1 to stop AI from interacting via Click() + var/malfhacking = FALSE // 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 + var/explosive = FALSE //does the AI explode when it dies? + + var/mob/living/silicon/ai/parent + var/camera_light_on = FALSE + var/list/obj/machinery/camera/lit_cameras = list() + + var/datum/trackable/track = new + + var/last_paper_seen = null + var/can_shunt = TRUE + var/last_announcement = "" // For AI VOX, if enabled + var/turf/waypoint //Holds the turf of the currently selected waypoint. + var/waypoint_mode = FALSE //Waypoint mode is for selecting a turf via clicking. + var/call_bot_cooldown = 0 //time of next call bot command + var/apc_override = FALSE //hack for letting the AI use its APC even when visionless + var/nuking = FALSE + var/obj/machinery/doomsday_device/doomsday_device + + var/mob/camera/aiEye/eyeobj + var/sprint = 10 + var/cooldown = 0 + var/acceleration = 1 + + var/obj/structure/AIcore/deactivated/linked_core //For exosuit control + var/mob/living/silicon/robot/deployed_shell = null //For shell control + var/datum/action/innate/deploy_shell/deploy_action = new + var/datum/action/innate/deploy_last_shell/redeploy_action = new + var/chnotify = 0 + + + 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 + var/display_icon_override + +/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai) + . = ..() + if(!target_ai) //If there is no player/brain inside. + new/obj/structure/AIcore/deactivated(loc) //New empty terminal. + return INITIALIZE_HINT_QDEL //Delete AI. + + ADD_TRAIT(src, TRAIT_NO_TELEPORT, src) + if(L && istype(L, /datum/ai_laws)) + laws = L + laws.associate(src) + else + make_laws() + + if(target_ai.mind) + target_ai.mind.transfer_to(src) + if(mind.special_role) + mind.store_memory("As an AI, you must obey your silicon laws above all else. Your objectives will consider you to be dead.") + to_chat(src, "You have been installed as an AI! ") + to_chat(src, "You must obey your silicon laws above all else. Your objectives will consider you to be dead.") + + 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.") + to_chat(src, "For department channels, use the following say commands:") + to_chat(src, ":o - AI Private, :c - Command, :s - Security, :e - Engineering, :u - Supply, :v - Service, :m - Medical, :n - Science.") + show_laws() + to_chat(src, "These laws may be changed by other players, or by you being the traitor.") + + job = "AI" + + create_eye() + apply_pref_name("ai") + + set_core_display_icon() + + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"default")) + + spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + + verbs += /mob/living/silicon/ai/proc/show_laws_verb + + aiPDA = new/obj/item/pda/ai(src) + aiPDA.owner = name + aiPDA.ownjob = "AI" + aiPDA.name = name + " (" + aiPDA.ownjob + ")" + + aiMulti = new(src) + radio = new /obj/item/radio/headset/ai(src) + aicamera = new/obj/item/camera/siliconcam/ai_camera(src) + + deploy_action.Grant(src) + + if(isturf(loc)) + verbs.Add(/mob/living/silicon/ai/proc/ai_network_change, \ + /mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \ + /mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \ + /mob/living/silicon/ai/proc/set_automatic_say_channel) + + GLOB.ai_list += src + GLOB.shuttle_caller_list += src + + builtInCamera = new (src) + builtInCamera.network = list("ss13") + + +/mob/living/silicon/ai/Destroy() + GLOB.ai_list -= src + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + qdel(eyeobj) // No AI, no Eye + malfhack = null + + . = ..() + +/mob/living/silicon/ai/IgniteMob() + fire_stacks = 0 + . = ..() + +/mob/living/silicon/ai/proc/set_core_display_icon(input, client/C) + if(client && !C) + C = client + if(!input && !C?.prefs?.preferred_ai_core_display) + icon_state = initial(icon_state) + else + var/preferred_icon = input ? input : C.prefs.preferred_ai_core_display + icon_state = resolve_ai_icon(preferred_icon) + +/mob/living/silicon/ai/verb/pick_icon() + set category = "AI Commands" + set name = "Set AI Core Display" + if(incapacitated()) + return + var/list/iconstates = GLOB.ai_core_display_screens + for(var/option in iconstates) + if(option == "Random") + iconstates[option] = image(icon = src.icon, icon_state = "ai-random") + continue + iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option)) + + view_core() + var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42) + + if(!ai_core_icon || incapacitated()) + return + display_icon_override = ai_core_icon + set_core_display_icon(ai_core_icon) + +/mob/living/silicon/ai/Stat() + ..() + if(statpanel("Status")) + if(!stat) + stat(null, text("System integrity: [(health+100)/2]%")) + stat(null, text("Connected cyborgs: [connected_robots.len]")) + for(var/mob/living/silicon/robot/R in connected_robots) + var/robot_status = "Nominal" + if(R.shell) + robot_status = "AI SHELL" + else 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! + stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ + Module: [R.designation] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) + stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells + else + stat(null, text("Systems nonfunctional")) + +/mob/living/silicon/ai/proc/ai_alerts() + var/dat = "Current Station Alerts\n" + dat += "Close

" + for (var/cat in alarms) + dat += text("[]
\n", cat) + var/list/L = alarms[cat] + if (L.len) + for (var/alarm in L) + var/list/alm = L[alarm] + var/area/A = alm[1] + var/C = alm[2] + var/list/sources = alm[3] + dat += "" + if (C && istype(C, /list)) + var/dat2 = "" + for (var/obj/machinery/camera/I in C) + dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) + dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera") + else if (C && istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/Ctmp = C + dat += text("-- [] ([])", A.name, Ctmp.c_tag) + else + dat += text("-- [] (No Camera)", A.name) + if (sources.len > 1) + dat += text("- [] sources", sources.len) + dat += "
\n" + else + dat += "-- All Systems Nominal
\n" + dat += "
\n" + + viewalerts = 1 + src << browse(dat, "window=aialerts&can_close=0") + +/mob/living/silicon/ai/proc/ai_roster() + var/dat = "Crew RosterCrew Roster:

" + + dat += GLOB.data_core.get_manifest() + dat += "" + + src << browse(dat, "window=airoster") + onclose(src, "airoster") + +/mob/living/silicon/ai/proc/ai_call_shuttle() + if(control_disabled) + to_chat(usr, "Wireless control is disabled!") + return + + var/reason = input(src, "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call") as null|text + + if(incapacitated()) + return + + if(trim(reason)) + SSshuttle.requestEvac(src, reason) + + // hack to display shuttle timer + if(!EMERGENCY_IDLE_OR_RECALLED) + var/obj/machinery/computer/communications/C = locate() in GLOB.machines + if(C) + C.post_status("shuttle") + +/mob/living/silicon/ai/can_interact_with(atom/A) + . = ..() + var/turf/ai = get_turf(src) + var/turf/target = get_turf(A) + if (.) + return + + if(!target) + return + + if ((ai.z != target.z) && !is_station_level(ai.z)) + return FALSE + + if (istype(loc, /obj/item/aicard)) + var/turf/T0 = get_turf(src) + var/turf/T1 = get_turf(A) + if (!T0 || ! T1) + return FALSE + return ISINRANGE(T1.x, T0.x - interaction_range, T0.x + interaction_range) && ISINRANGE(T1.y, T0.y - interaction_range, T0.y + interaction_range) + else + return GLOB.cameranet.checkTurfVis(get_turf(A)) + +/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(incapacitated()) + return + var/is_anchored = FALSE + if(move_resist == MOVE_FORCE_OVERPOWERING) + move_resist = MOVE_FORCE_NORMAL + else + is_anchored = TRUE + move_resist = MOVE_FORCE_OVERPOWERING + REMOVE_TRAIT(src, TRAIT_NO_TELEPORT, src) + ADD_TRAIT(src, TRAIT_NO_TELEPORT, src) + + to_chat(src, "You are now [is_anchored ? "" : "un"]anchored.") + // the message in the [] will change depending whether or not the AI is anchored + +/mob/living/silicon/ai/update_canmove() //If the AI dies, mobs won't go through it anymore + return 0 + +/mob/living/silicon/ai/proc/ai_cancel_call() + set category = "Malfunction" + if(control_disabled) + to_chat(src, "Wireless control is disabled!") + return + SSshuttle.cancelEvac(src) + +/mob/living/silicon/ai/restrained(ignore_grab) + . = 0 + +/mob/living/silicon/ai/Topic(href, href_list) + if(usr != src || incapacitated()) + 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 GLOB.cameranet.cameras + if (href_list["showalerts"]) + ai_alerts() +#ifdef AI_VOX + if(href_list["say_word"]) + play_vox_word(href_list["say_word"], null, src) + return +#endif + 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/holopad/H = locate(href_list["jumptoholopad"]) + if(H) + H.attack_ai(src) //may as well recycle + else + to_chat(src, "Unable to locate the holopad.") + if(href_list["track"]) + var/string = href_list["track"] + trackable_mobs() + var/list/trackeable = list() + trackeable += track.humans + track.others + var/list/target = list() + for(var/I in trackeable) + var/mob/M = trackeable[I] + if(M.name == string) + target += M + if(name == string) + target += src + if(target.len) + ai_actual_track(pick(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. + if(call_bot_cooldown > world.time) + to_chat(src, "Error: Your last call bot command is still processing, please wait for the bot to finish calculating a route.") + return + Bot = locate(href_list["callbot"]) in GLOB.alive_mob_list + if(!Bot || Bot.remote_disabled || src.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.alive_mob_list + if(!Bot || Bot.remote_disabled || src.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(controlled_mech) + to_chat(src, "You are already loaded into an onboard computer!") + return + if(!GLOB.cameranet.checkCameraVis(M)) + to_chat(src, "Exosuit is no longer near active cameras.") + 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. + + +/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C) + if(QDELETED(C)) + return FALSE + + if(!tracking) + cameraFollow = null + + if(QDELETED(eyeobj)) + view_core() + return + // ok, we're alive, camera is good and in our network... + eyeobj.setLoc(get_turf(C)) + return TRUE + +/mob/living/silicon/ai/proc/botcall() + set category = "AI Commands" + set name = "Access Robot Control" + set desc = "Wirelessly control various automatic robots." + if(incapacitated()) + return + + if(control_disabled) + to_chat(src, "Wireless control is disabled.") + return + var/turf/ai_current_turf = get_turf(src) + var/ai_Zlevel = ai_current_turf.z + var/d + d += "Query network status
" + d += "

Name

Status

Location

Control

[Bot.hacked ? "(!)" : ""] [Bot.name] ([Bot.model])[bot_mode][get_area_name(Bot, TRUE)]InterfaceCall
" + + for (Bot in GLOB.alive_mob_list) + if(Bot.z == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected! + var/bot_mode = Bot.get_mode() + 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(GLOB.cameranet && GLOB.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 + to_chat(src, "Sending command to bot...") + call_bot_cooldown = world.time + CALL_BOT_COOLDOWN + Bot.call_bot(src, waypoint) + call_bot_cooldown = 0 + + +/mob/living/silicon/ai/triggerAlarm(class, area/A, O, obj/alarmsource) + if(alarmsource.z != z) + return + var/list/L = alarms[class] + for (var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/sources = alarm[3] + if (!(alarmsource in sources)) + sources += alarmsource + return 1 + var/obj/machinery/camera/C = null + var/list/CL = null + if (O && istype(O, /list)) + CL = O + if (CL.len == 1) + C = CL[1] + else if (O && istype(O, /obj/machinery/camera)) + C = O + L[A.name] = list(A, (C) ? C : O, list(alarmsource)) + if (O) + if (C && C.can_use()) + queueAlarm("--- [class] alarm detected in [A.name]! ([C.c_tag])", class) + else if (CL && CL.len) + var/foo = 0 + var/dat2 = "" + for (var/obj/machinery/camera/I in CL) + dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... + foo = 1 + queueAlarm(text ("--- [] alarm detected in []! ([])", class, A.name, dat2), class) + else + queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) + else + queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) + if (viewalerts) ai_alerts() + return 1 + +/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin) + var/list/L = alarms[class] + var/cleared = 0 + for (var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/srcs = alarm[3] + if (origin in srcs) + srcs -= origin + if (srcs.len == 0) + cleared = 1 + L -= I + if (cleared) + queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) + if (viewalerts) ai_alerts() + return !cleared + +//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() + cameraFollow = null + var/cameralist[0] + + if(incapacitated()) + return + + var/mob/living/silicon/ai/U = usr + + for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) + var/list/tempnetwork = C.network + if(!(is_station_level(C.z) || is_mining_level(C.z) || ("ss13" in tempnetwork))) + continue + if(!C.can_use()) + continue + + tempnetwork.Remove("rd", "toxins", "prison") + if(tempnetwork.len) + for(var/i in C.network) + cameralist[i] = i + var/old_network = network + network = input(U, "Which network would you like to view?") as null|anything in cameralist + + 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 GLOB.cameranet.cameras) + if(!C.can_use()) + continue + if(network in C.network) + U.eyeobj.setLoc(get_turf(C)) + break + to_chat(src, "Switched to the \"[uppertext(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(incapacitated()) + return + var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Thinking", "Friend Computer", "Dorfy", "Blue Glow", "Red Glow") + var/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions + for (var/each in GLOB.ai_status_displays) //change status of displays + var/obj/machinery/status_display/ai/M = each + M.emotion = emote + M.update() + if (emote == "Friend Computer") + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = "friendcomputer")) + frequency.post_signal(src, status_signal) + 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(incapacitated()) + return + 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/list/personnel_list = list() + + for(var/datum/data/record/t in GLOB.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/list/icon_list = list( + "bear" = 'icons/mob/animal.dmi', + "carp" = 'icons/mob/animal.dmi', + "chicken" = 'icons/mob/animal.dmi', + "corgi" = 'icons/mob/pets.dmi', + "cow" = 'icons/mob/animal.dmi', + "crab" = 'icons/mob/animal.dmi', + "fox" = 'icons/mob/pets.dmi', + "goat" = 'icons/mob/animal.dmi', + "cat" = 'icons/mob/pets.dmi', + "cat2" = 'icons/mob/pets.dmi', + "poly" = 'icons/mob/animal.dmi', + "pug" = 'icons/mob/pets.dmi', + "spider" = 'icons/mob/animal.dmi' + ) + + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("poly") + holo_icon = getHologramIcon(icon(icon_list[input],"parrot_fly")) + if("chicken") + holo_icon = getHologramIcon(icon(icon_list[input],"chicken_brown")) + if("spider") + holo_icon = getHologramIcon(icon(icon_list[input],"guard")) + else + holo_icon = getHologramIcon(icon(icon_list[input], input)) + else + var/list/icon_list = list( + "default" = 'icons/mob/ai.dmi', + "floating face" = 'icons/mob/ai.dmi', + "xeno queen" = 'icons/mob/alien.dmi', + "horror" = 'icons/mob/ai.dmi' + ) + + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("xeno queen") + holo_icon = getHologramIcon(icon(icon_list[input],"alienq")) + else + holo_icon = getHologramIcon(icon(icon_list[input], input)) + return + +/mob/living/silicon/ai/proc/corereturn() + set category = "Malfunction" + set name = "Return to Main Core" + + var/obj/machinery/power/apc/apc = src.loc + if(!istype(apc)) + to_chat(src, "You are already in your Main Core.") + return + apc.malfvacate() + +/mob/living/silicon/ai/proc/toggle_camera_light() + 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.") + +//AI_CAMERA_LUMINOSITY + +/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 || !C.internal_light) + 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/proc/control_integrated_radio() + set name = "Transceiver Settings" + set desc = "Allows you to change settings of your radio." + set category = "AI Commands" + + if(incapacitated()) + return + + to_chat(src, "Accessing Subspace Transceiver control...") + if (radio) + radio.interact(src) + +/mob/living/silicon/ai/proc/set_syndie_radio() + if(radio) + radio.make_syndie() + +/mob/living/silicon/ai/proc/set_automatic_say_channel() + set name = "Set Auto Announce Mode" + set desc = "Modify the default radio setting for your automatic announcements." + set category = "AI Commands" + + if(incapacitated()) + return + set_autosay() + +/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(!can_be_carded) + to_chat(user, "Transfer failed.") + return + disconnect_shell() //If the AI is controlling a borg, force the player back to core! + if(!mind) + to_chat(user, "No intelligence patterns detected." ) + return + ShutOffDoomsdayDevice() + new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. + ai_restore_power()//So the AI initially has power. + control_disabled = 1//Can't control things remotely if you're stuck in a card! + radio_enabled = 0 //No talking on the built-in radio for you either! + forceMove(card) + card.AI = src + 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 0 + +/mob/living/silicon/ai/incapacitated() + if(aiRestorePowerRoutine) + return TRUE + return ..() + +/mob/living/silicon/ai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) + if(control_disabled || incapacitated()) + to_chat(src, "You can't do that right now!") + return FALSE + if(be_close && !in_range(M, src)) + to_chat(src, "You are too far away!") + return FALSE + return can_see(M) //stop AIs from leaving windows open and using then after they lose vision + +/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 + return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(get_turf_pixel(A))) || 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(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) + var/treated_message = lang_treat(speaker, message_language, raw_message, spans, message_mode) + var/start = "Relayed Speech: " + var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]" + var/hrefpart = "" + var/jobpart + + if (iscarbon(speaker)) + var/mob/living/carbon/S = speaker + if(S.job) + jobpart = "[S.job]" + else + jobpart = "Unknown" + + var/rendered = "[start][hrefpart][namepart] ([jobpart])[treated_message]" + if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) + + show_message(rendered, MSG_AUDIBLE) + +/mob/living/silicon/ai/fully_replace_character_name(oldname,newname) + ..() + if(oldname != real_name) + if(eyeobj) + eyeobj.name = "[newname] (AI Eye)" + + // Notify Cyborgs + for(var/mob/living/silicon/robot/Slave in connected_robots) + Slave.show_laws() + +/mob/living/silicon/ai/replace_identification_name(oldname,newname) + if(aiPDA) + aiPDA.owner = newname + aiPDA.name = newname + " (" + aiPDA.ownjob + ")" + + +/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/reset_perspective(atom/A) + if(camera_light_on) + light_cameras() + if(istype(A, /obj/machinery/camera)) + current = A + if(client) + if(ismovableatom(A)) + if(A != GLOB.ai_camera_room_landmark) + end_multicam() + client.perspective = EYE_PERSPECTIVE + client.eye = A + else + end_multicam() + if(isturf(loc)) + if(eyeobj) + client.eye = eyeobj + client.perspective = EYE_PERSPECTIVE + else + client.eye = client.mob + client.perspective = MOB_PERSPECTIVE + else + client.perspective = EYE_PERSPECTIVE + client.eye = loc + update_sight() + if(client.eye != src) + var/atom/AT = client.eye + AT.get_remote_view_fullscreens(src) + else + clear_fullscreen("remote_view", 0) + +/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0) + . = ..() + if(.) //successfully ressuscitated from death + set_core_display_icon(display_icon_override) + +/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. \The [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 + apc.coverlocked = TRUE + + playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1) + to_chat(src, "Hack complete. \The [apc] is now under your exclusive control.") + apc.update_icon() + +/mob/living/silicon/ai/verb/deploy_to_shell(var/mob/living/silicon/robot/target) + set category = "AI Commands" + set name = "Deploy to Shell" + + if(incapacitated()) + return + if(control_disabled) + to_chat(src, "Wireless networking module is offline.") + return + + var/list/possible = list() + + for(var/borgie in GLOB.available_ai_shells) + var/mob/living/silicon/robot/R = borgie + if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai ||(R.connected_ai == src))) + possible += R + + if(!LAZYLEN(possible)) + to_chat(src, "No usable AI shell beacons detected.") + + if(!target || !(target in possible)) //If the AI is looking for a new shell, or its pre-selected shell is no longer valid + target = input(src, "Which body to control?") as null|anything in possible + + if (!target || target.stat == DEAD || target.deployed || !(!target.connected_ai ||(target.connected_ai == src))) + return + + else if(mind) + soullink(/datum/soullink/sharedbody, src, target) + deployed_shell = target + target.deploy_init(src) + mind.transfer_to(target) + diag_hud_set_deployed() + +/datum/action/innate/deploy_shell + name = "Deploy to AI Shell" + desc = "Wirelessly control a specialized cyborg shell." + icon_icon = 'icons/mob/actions/actions_AI.dmi' + button_icon_state = "ai_shell" + +/datum/action/innate/deploy_shell/Trigger() + var/mob/living/silicon/ai/AI = owner + if(!AI) + return + AI.deploy_to_shell() + +/datum/action/innate/deploy_last_shell + name = "Reconnect to shell" + desc = "Reconnect to the most recently used AI shell." + icon_icon = 'icons/mob/actions/actions_AI.dmi' + button_icon_state = "ai_last_shell" + var/mob/living/silicon/robot/last_used_shell + +/datum/action/innate/deploy_last_shell/Trigger() + if(!owner) + return + if(last_used_shell) + var/mob/living/silicon/ai/AI = owner + AI.deploy_to_shell(last_used_shell) + else + Remove(owner) //If the last shell is blown, destroy it. + +/mob/living/silicon/ai/proc/disconnect_shell() + if(deployed_shell) //Forcibly call back AI in event of things such as damage, EMP or power loss. + to_chat(src, "Your remote connection has been reset!") + deployed_shell.undeploy() + diag_hud_set_deployed() + +/mob/living/silicon/ai/resist() + return + +/mob/living/silicon/ai/spawned/Initialize(mapload, datum/ai_laws/L, mob/target_ai) + . = ..() + if(!target_ai) + target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct + +/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye) + GLOB.cameranet.visibility(moved_eye, client, all_eyes, USE_STATIC_OPAQUE) + +/mob/living/silicon/ai/forceMove(atom/destination) + . = ..() + if(.) + end_multicam() 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 d2b23d5e..0e3cde56 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -80,7 +80,7 @@ Difficulty: Medium return FALSE return ..() -/mob/living/simple_animal/hostile/megafauna/dragon/visible_message() +/mob/living/simple_animal/hostile/megafauna/dragon/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs) if(swooping & SWOOP_INVULNERABLE) //to suppress attack messages without overriding every single proc that could send a message saying we got hit return return ..() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 3d60ea43..87681881 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -87,106 +87,120 @@ msg = copytext(msg, 1, MAX_MESSAGE_LEN) if(type) - if(type & 1 && eye_blind )//Vision related + if(type & MSG_VISUAL && eye_blind )//Vision related if(!alt_msg) return else msg = alt_msg type = alt_type - if(type & 2 && !can_hear())//Hearing related + if(type & MSG_AUDIBLE && !can_hear())//Hearing related if(!alt_msg) return else msg = alt_msg type = alt_type - if(type & 1 && eye_blind) + if(type & MSG_VISUAL && eye_blind) return // voice muffling if(stat == UNCONSCIOUS) - if(type & 2) //audio + if(type & MSG_AUDIBLE) //audio to_chat(src, "... You can almost hear something ...") - else - to_chat(src, msg) + return + to_chat(src, msg) -// Show a message to all player mobs who sees this atom -// Show a message to the src mob (if the src is a mob) -// Use for atoms performing visible actions -// message is output to anyone who can see, e.g. "The [src] does something!" -// self_message (optional) is what the src mob sees e.g. "You do something!" -// blind_message (optional) is what blind people will hear e.g. "You hear something!" -// vision_distance (optional) define how many tiles away the message can be seen. -// ignored_mob (optional) doesn't show any message to a given mob if TRUE. - -/atom/proc/visible_message(message, self_message, blind_message, vision_distance, list/ignored_mobs, no_ghosts = FALSE) +/** + * Generate a visible message from this atom + * + * Show a message to all player mobs who sees this atom + * + * Show a message to the src mob (if the src is a mob) + * + * Use for atoms performing visible actions + * + * message is output to anyone who can see, e.g. "The [src] does something!" + * + * Vars: + * * self_message (optional) is what the src mob sees e.g. "You do something!" + * * blind_message (optional) is what blind people will hear e.g. "You hear something!" + * * vision_distance (optional) define how many tiles away the message can be seen. + * * ignored_mobs (optional) doesn't show any message to any given mob in the list. + */ +/atom/proc/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs) var/turf/T = get_turf(src) if(!T) return + var/list/hearers = get_hearers_in_view(vision_distance, src) //caches the hearers and then removes ignored mobs. + if(!length(hearers)) + return if(!islist(ignored_mobs)) ignored_mobs = list(ignored_mobs) - var/range = 7 - if(vision_distance) - range = vision_distance - for(var/mob/M in get_hearers_in_view(range, src)) + hearers -= ignored_mobs + if(self_message) + hearers -= src + for(var/mob/M in hearers) if(!M.client) continue - if(M in ignored_mobs) - continue + //This entire if/else chain could be in two lines but isn't for readibilties sake. var/msg = message - if(isobserver(M) && no_ghosts) + //CITADEL EDIT, required for vore code to remove (T != loc && T != src)) as a check + if(M.see_invisible[user.say_emphasis(message)]" if(emote_type == EMOTE_AUDIBLE) - user.audible_message(message=message,hearing_distance=1, no_ghosts = TRUE) + user.audible_message(message=message,hearing_distance=1, ignored_mobs = GLOB.dead_mob_list) else - user.visible_message(message=message,self_message=message,vision_distance=1, no_ghosts = TRUE) + user.visible_message(message=message,self_message=message,vision_distance=1, ignored_mobs = GLOB.dead_mob_list) log_emote("[key_name(user)] : (SUBTLER) [message]") message = null diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 10214f12..8f416cc0 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -719,10 +719,10 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) for(var/mob/living/L in range(10)) investigate_log("has irradiated [key_name(L)] after consuming [AM].", INVESTIGATE_SUPERMATTER) if(L in view()) - L.show_message("As \the [src] slowly stops resonating, you find your skin covered in new radiation burns.", 1,\ - "The unearthly ringing subsides and you notice you have new radiation burns.", 2) + L.show_message("As \the [src] slowly stops resonating, you find your skin covered in new radiation burns.", MSG_VISUAL,\ + "The unearthly ringing subsides and you notice you have new radiation burns.", MSG_AUDIBLE) else - L.show_message("You hear an unearthly ringing and notice your skin is covered in fresh radiation burns.", 2) + L.show_message("You hear an unearthly ringing and notice your skin is covered in fresh radiation burns.", MSG_AUDIBLE) //Do not blow up our internal radio /obj/machinery/power/supermatter_crystal/contents_explosion(severity, target) diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm index 05d63673..99102a2d 100644 --- a/code/modules/projectiles/pins.dm +++ b/code/modules/projectiles/pins.dm @@ -58,10 +58,10 @@ /obj/item/firing_pin/proc/auth_fail(mob/living/user) if(user) - user.show_message(fail_message, 1) + user.show_message(fail_message, MSG_VISUAL) if(selfdestruct) if(user) - user.show_message("SELF-DESTRUCTING...
", 1) + user.show_message("SELF-DESTRUCTING...
", MSG_VISUAL) to_chat(user, "[gun] explodes!") explosion(get_turf(gun), -1, 0, 2, 3) if(gun) diff --git a/code/modules/recycling/disposal/holder.dm b/code/modules/recycling/disposal/holder.dm index 919d1d4f..852f0b67 100644 --- a/code/modules/recycling/disposal/holder.dm +++ b/code/modules/recycling/disposal/holder.dm @@ -115,7 +115,7 @@ if(user.incapacitated()) return for(var/mob/M in range(5, get_turf(src))) - M.show_message("CLONG, clong!", 2) + M.show_message("CLONG, clong!", MSG_AUDIBLE) playsound(src.loc, 'sound/effects/clang.ogg', 50, 0, 0) // called to vent all gas in holder to a location diff --git a/modular_citadel/code/modules/vore/eating/belly_obj_vr.dm b/modular_citadel/code/modules/vore/eating/belly_obj_vr.dm index 1ad29c1a..dbd831f2 100644 --- a/modular_citadel/code/modules/vore/eating/belly_obj_vr.dm +++ b/modular_citadel/code/modules/vore/eating/belly_obj_vr.dm @@ -542,7 +542,7 @@ var/list/watching = hearers(3, owner) for(var/mob/living/M in watching) if(M.client && (M.client.prefs.cit_toggles & EATING_NOISES)) //Might as well censor the normies here too. - M.show_message(struggle_outer_message, 1) // visible + M.show_message(struggle_outer_message, MSG_VISUAL) // visible to_chat(R,struggle_user_message) From 5d80346f8fd028368e31044220419ba991b15a3a Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Thu, 5 Dec 2019 21:44:25 -0700 Subject: [PATCH 07/21] Merge pull request #9993 from Ghommie/Ghommie-cit412 Ports tgstation loom changes, and durathred golems. --- code/__DEFINES/status_effects.dm | 2 + code/__DEFINES/traits.dm | 1 + code/datums/status_effects/debuffs.dm | 32 + code/game/objects/items/tools/wirecutters.dm | 277 +- .../objects/structures/ghost_role_spawners.dm | 3 + code/game/objects/structures/loom.dm | 63 +- code/modules/cargo/packs/materials.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 2289 +++++++++-------- .../mob/living/carbon/human/human_defense.dm | 1610 ++++++------ .../mob/living/carbon/human/species.dm | 4 + .../carbon/human/species_types/golems.dm | 15 + code/modules/mob/living/carbon/life.dm | 2 +- code/modules/ruins/lavaland_ruin_code.dm | 3 +- 13 files changed, 2177 insertions(+), 2126 deletions(-) diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 1ab4fd3f..4cc12b46 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -58,6 +58,8 @@ #define MAX_MANIA_SEVERITY 100 //how high the mania severity can go #define MANIA_DAMAGE_TO_CONVERT 90 //how much damage is required before it'll convert affected targets +#define STATUS_EFFECT_CHOKINGSTRAND /datum/status_effect/strandling //Choking Strand + #define STATUS_EFFECT_HISWRATH /datum/status_effect/his_wrath //His Wrath. #define STATUS_EFFECT_SUMMONEDGHOST /datum/status_effect/cultghost //is a cult ghost and can't use manifest runes diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 3bfb2da0..01297457 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -126,6 +126,7 @@ #define TRAIT_LAW_ENFORCEMENT_METABOLISM "law-enforcement-metabolism" #define TRAIT_STRONG_GRABBER "strong_grabber" #define TRAIT_CALCIUM_HEALER "calcium_healer" +#define TRAIT_MAGIC_CHOKE "magic_choke" #define TRAIT_ALCOHOL_LIGHTWEIGHT "alcohol_lightweight" //Skyrat port #define TRAIT_CURSED_BLOOD "cursed_blood" //Yo dawg I heard you like bloodborne references so I put a #define TRAIT_HEAT_DETECT "heat_detect" diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index a9ba87b1..237afdd5 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -609,6 +609,38 @@ icon_state = "ichorial_stain" alerttooltipstyle = "clockcult" + +//GOLEM GANG + +/datum/status_effect/strandling //get it, strand as in durathread strand + strangling = strandling hahahahahahahahahahhahahaha i want to die + id = "strandling" + status_type = STATUS_EFFECT_UNIQUE + alert_type = /obj/screen/alert/status_effect/strandling + +/datum/status_effect/strandling/on_apply() + ADD_TRAIT(owner, TRAIT_MAGIC_CHOKE, "dumbmoron") + return ..() + +/datum/status_effect/strandling/on_remove() + REMOVE_TRAIT(owner, TRAIT_MAGIC_CHOKE, "dumbmoron") + return ..() + +/obj/screen/alert/status_effect/strandling + name = "Choking strand" + desc = "A magical strand of Durathread is wrapped around your neck, preventing you from breathing! Click this icon to remove the strand." + icon_state = "his_grace" + alerttooltipstyle = "hisgrace" + +/obj/screen/alert/status_effect/strandling/Click(location, control, params) + . = ..() + to_chat(mob_viewer, "You attempt to remove the durathread strand from around your neck.") + if(do_after(mob_viewer, 35, null, mob_viewer)) + if(isliving(mob_viewer)) + var/mob/living/L = mob_viewer + to_chat(mob_viewer, "You succesfuly remove the durathread strand.") + L.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + + datum/status_effect/pacify id = "pacify" status_type = STATUS_EFFECT_REPLACE diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm index dc4353d1..8586fd47 100644 --- a/code/game/objects/items/tools/wirecutters.dm +++ b/code/game/objects/items/tools/wirecutters.dm @@ -1,135 +1,144 @@ -/obj/item/wirecutters - name = "wirecutters" - desc = "This cuts wires." - icon = 'icons/obj/tools.dmi' - icon_state = "cutters_map" - item_state = "cutters" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 6 - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=80) - attack_verb = list("pinched", "nipped") - hitsound = 'sound/items/wirecutter.ogg' - usesound = 'sound/items/wirecutter.ogg' - - tool_behaviour = TOOL_WIRECUTTER - 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 - var/static/list/wirecutter_colors = list( - "blue" = "#1861d5", - "red" = "#951710", - "pink" = "#d5188d", - "brown" = "#a05212", - "green" = "#0e7f1b", - "cyan" = "#18a2d5", - "yellow" = "#d58c18" - ) - - -/obj/item/wirecutters/Initialize() - . = ..() - if(random_color) //random colors! - icon_state = "cutters" - var/our_color = pick(wirecutter_colors) - add_atom_colour(wirecutter_colors[our_color], FIXED_COLOUR_PRIORITY) - update_icon() - -/obj/item/wirecutters/update_icon() - if(!random_color) //icon override - return - cut_overlays() - var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") - base_overlay.appearance_flags = RESET_COLOR - add_overlay(base_overlay) - -/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(C.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." - resistance_flags = FIRE_PROOF | ACID_PROOF - icon_state = "cutters_brass" - random_color = FALSE - toolspeed = 0.5 - -/obj/item/wirecutters/bronze - name = "bronze plated wirecutters" - desc = "A pair of wirecutters plated with bronze." - icon_state = "cutters_brass" - random_color = FALSE - toolspeed = 0.95 //Wire cutters have 0 time bars though - -/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 - 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, compressed through the magic of science. It's fitted with a cutting head." - icon_state = "jaws_cutter" - item_state = "jawsoflife" - - 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(iscarbon(user)) - var/mob/living/carbon/C = user - var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) - if(BP) - BP.drop_limb() - 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(drop_location()) - to_chat(user, "You attach the pry jaws to [src].") - qdel(src) - user.put_in_active_hand(pryjaws) - -/obj/item/wirecutters/power/attack(mob/living/carbon/C, mob/user) - if(istype(C) && C.handcuffed) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - qdel(C.handcuffed) - return - else - ..() - -/obj/item/wirecutters/advanced - name = "advanced wirecutters" - desc = "A set of reproduction alien wirecutters, they have a silver handle with an exceedingly sharp blade." - icon = 'icons/obj/advancedtools.dmi' - icon_state = "cutters" - toolspeed = 0.2 +/obj/item/wirecutters + name = "wirecutters" + desc = "This cuts wires." + icon = 'icons/obj/tools.dmi' + icon_state = "cutters_map" + item_state = "cutters" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 6 + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=80) + attack_verb = list("pinched", "nipped") + hitsound = 'sound/items/wirecutter.ogg' + usesound = 'sound/items/wirecutter.ogg' + + tool_behaviour = TOOL_WIRECUTTER + 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 + var/static/list/wirecutter_colors = list( + "blue" = "#1861d5", + "red" = "#951710", + "pink" = "#d5188d", + "brown" = "#a05212", + "green" = "#0e7f1b", + "cyan" = "#18a2d5", + "yellow" = "#d58c18" + ) + + +/obj/item/wirecutters/Initialize() + . = ..() + if(random_color) //random colors! + icon_state = "cutters" + var/our_color = pick(wirecutter_colors) + add_atom_colour(wirecutter_colors[our_color], FIXED_COLOUR_PRIORITY) + update_icon() + +/obj/item/wirecutters/update_icon() + if(!random_color) //icon override + return + cut_overlays() + var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") + base_overlay.appearance_flags = RESET_COLOR + add_overlay(base_overlay) + +/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(C.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." + resistance_flags = FIRE_PROOF | ACID_PROOF + icon_state = "cutters_brass" + random_color = FALSE + toolspeed = 0.5 + +/obj/item/wirecutters/bronze + name = "bronze plated wirecutters" + desc = "A pair of wirecutters plated with bronze." + icon_state = "cutters_brass" + random_color = FALSE + toolspeed = 0.95 //Wire cutters have 0 time bars though + +/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 + 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, compressed through the magic of science. It's fitted with a cutting head." + icon_state = "jaws_cutter" + item_state = "jawsoflife" + + 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(iscarbon(user)) + var/mob/living/carbon/C = user + var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) + if(BP) + BP.drop_limb() + 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(drop_location()) + to_chat(user, "You attach the pry jaws to [src].") + qdel(src) + user.put_in_active_hand(pryjaws) + +/obj/item/wirecutters/power/attack(mob/living/carbon/C, mob/user) + if(istype(C)) + if(C.handcuffed) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + qdel(C.handcuffed) + return + else if(C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) + var/man = C == user ? "your" : "[C]'\s" + user.visible_message("[user] attempts to remove the durathread strand from around [man] neck.", \ + "You attempt to remove the durathread strand from around [man] neck.") + if(do_after(user, 15, null, C)) + user.visible_message("[user] succesfuly removes the durathread strand.", + "You succesfuly remove the durathread strand.") + C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + return + ..() + +/obj/item/wirecutters/advanced + name = "advanced wirecutters" + desc = "A set of reproduction alien wirecutters, they have a silver handle with an exceedingly sharp blade." + icon = 'icons/obj/advancedtools.dmi' + icon_state = "cutters" + toolspeed = 0.2 random_color = FALSE \ No newline at end of file diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm index af2d7407..4b0b944f 100644 --- a/code/game/objects/structures/ghost_role_spawners.dm +++ b/code/game/objects/structures/ghost_role_spawners.dm @@ -154,6 +154,9 @@ log_admin("[key_name(new_spawn)] possessed a golem shell enslaved to [key_name(owner)].") if(ishuman(new_spawn)) var/mob/living/carbon/human/H = new_spawn + if(has_owner) + var/datum/species/golem/G = H.dna.species + G.owner = owner H.set_cloned_appearance() if(!name) if(has_owner) diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm index 7d1ee27e..1b4cac9d 100644 --- a/code/game/objects/structures/loom.dm +++ b/code/game/objects/structures/loom.dm @@ -1,5 +1,7 @@ -//Loom, turns raw cotton and durathread into their respective fabrics. +#define FABRIC_PER_SHEET 4 + +///This is a loom. It's usually made out of wood and used to weave fabric like durathread or cotton into their respective cloth types. /obj/structure/loom name = "loom" desc = "A simple device used to weave cloth and other thread-based fabrics together into usable material." @@ -9,73 +11,34 @@ anchored = TRUE /obj/structure/loom/attackby(obj/item/I, mob/user) - if (user.a_intent != INTENT_HELP) - return ..() - if (istype(I, /obj/item/stack/sheet)) - if (!anchored) - return to_chat(user, "You have to anchor [src] first!") - - var/obj/item/stack/sheet/W = I - if(W.is_fabric && W.amount > 1) - user.visible_message("", - "You start weaving the [W.name] through the loom.") - - if(W.use_tool(src, user, W.pull_effort)) - new W.loom_result(drop_location()) - to_chat(user, "You weave the [W.name] into a workable fabric.") - W.amount = (W.amount - 2) - if(W.amount < 1) - qdel(W) - else if(W.is_fabric) - to_chat("You need at least 2 [W.name] to loom into fabric!") + if(weave(I, user)) return - else - switch (I.tool_behaviour) - if (TOOL_SCREWDRIVER) - user.visible_message("[user] starts disassembling [src]...", - "You start disassembling [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 60)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - deconstruct(TRUE) - return - if (TOOL_WRENCH) - if (anchored) - to_chat(user, "You unsecure the [src].") - I.play_tool_sound(src) - anchored = FALSE - else - to_chat(user, "You secure the [src].") - I.play_tool_sound(src) - anchored = TRUE - return return ..() -/obj/structure/loom/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood (get_turf(src), 10) - qdel(src) +/obj/structure/loom/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I, 5) + return TRUE - - -/*///Handles the weaving. +///Handles the weaving. /obj/structure/loom/proc/weave(obj/item/stack/sheet/S, mob/user) if(!istype(S) || !S.is_fabric) return FALSE if(!anchored) - user.show_message("The loom needs to be wrenched down.", MSG_VISUAL) + user.show_message("The loom needs to be wrenched down.", 1) return FALSE if(S.amount < FABRIC_PER_SHEET) user.show_message("You need at least [FABRIC_PER_SHEET] units of fabric before using this.", 1) return FALSE - user.show_message("You start weaving \the [S.name] through the loom..", MSG_VISUAL) + user.show_message("You start weaving \the [S.name] through the loom..", 1) if(S.use_tool(src, user, S.pull_effort)) if(S.amount >= FABRIC_PER_SHEET) new S.loom_result(drop_location()) S.use(FABRIC_PER_SHEET) - user.show_message("You weave \the [S.name] into a workable fabric.", MSG_VISUAL) + user.show_message("You weave \the [S.name] into a workable fabric.", 1) return TRUE /obj/structure/loom/unanchored anchored = FALSE -#undef FABRIC_PER_SHEET */ +#undef FABRIC_PER_SHEET diff --git a/code/modules/cargo/packs/materials.dm b/code/modules/cargo/packs/materials.dm index ab408ffe..af589879 100644 --- a/code/modules/cargo/packs/materials.dm +++ b/code/modules/cargo/packs/materials.dm @@ -195,7 +195,7 @@ name = "Loom" desc = "A large pre-made loom." cost = 1000 - contains = list(/obj/structure/loom) + contains = list(/obj/structure/loom/unanchored) crate_name = "loom crate" crate_type = /obj/structure/closet/crate/large diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d7e93a92..100e9f43 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1,1137 +1,1152 @@ -/mob/living/carbon/human - name = "Unknown" - real_name = "Unknown" - icon = 'icons/mob/human.dmi' - icon_state = "caucasian_m" - appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE - -/mob/living/carbon/human/Initialize() - verbs += /mob/living/proc/mob_sleep - verbs += /mob/living/proc/lay_down - verbs += /mob/living/carbon/human/proc/underwear_toggle //fwee - time_initialized = world.time - - //initialize limbs first - create_bodyparts() - - //initialize dna. for spawned humans; overwritten by other code - create_dna(src) - randomize_human(src) - dna.initialize_dna() - - if(dna.species) - set_species(dna.species.type) - - //initialise organs - create_internal_organs() //most of it is done in set_species now, this is only for parent call - physiology = new() - - handcrafting = new() - - . = ..() - - if(CONFIG_GET(flag/disable_stambuffer)) - togglesprint() - - AddComponent(/datum/component/redirect, list(COMSIG_COMPONENT_CLEAN_ACT = CALLBACK(src, /mob/living/carbon/human/clean_blood))) - - -/mob/living/carbon/human/ComponentInitialize() - . = ..() - if(!CONFIG_GET(flag/disable_human_mood)) - AddComponent(/datum/component/mood) - AddElement(/datum/element/mob_holder/micro, "micro") - -/mob/living/carbon/human/Destroy() - QDEL_NULL(physiology) - QDEL_NULL_LIST(vore_organs) // CITADEL EDIT belly stuff - return ..() - - -/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/Stat() - ..() - //Same thing from mob - if(statpanel("Status")) - if(tickrefresh == 1) - sList2 = list() - sList2 += "Intent: [a_intent]" - sList2 += "Move Mode: [m_intent]" - if (internal) - if (!internal.air_contents) - qdel(internal) - else - sList2 += "Internal Atmosphere Info: "+ "[internal.name]" - sList2 += "Tank Pressure: "+ "[internal.air_contents.return_pressure()]" - sList2 += "Distribution Pressure: "+ "[internal.distribute_pressure]" - if(mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - sList2 += "Chemical Storage: " + "[changeling.chem_charges]/[changeling.chem_storage]" - sList2 += "Absorbed DNA: "+ "[changeling.absorbedcount]" - if (sList2 != null) - stat(null, "[sList2.Join("\n\n")]") - - //NINJACODE - if(istype(wear_suit, /obj/item/clothing/suit/space/space_ninja)) //Only display if actually a ninja. - var/obj/item/clothing/suit/space/space_ninja/SN = wear_suit - if(statpanel("SpiderOS")) - stat("SpiderOS Status:","[SN.s_initialized ? "Initialized" : "Disabled"]") - stat("Current Time:", "[STATION_TIME_TIMESTAMP("hh:mm:ss")]") - if(SN.s_initialized) - //Suit gear - stat("Energy Charge:", "[round(SN.cell.charge/100)]%") - stat("Smoke Bombs:", "\Roman [SN.s_bombs]") - //Ninja status - stat("Fingerprints:", "[md5(dna.uni_identity)]") - stat("Unique Identity:", "[dna.unique_enzymes]") - stat("Overall Status:", "[stat > 1 ? "dead" : "[health]% healthy"]") - stat("Nutrition Status:", "[nutrition]") - stat("Oxygen Loss:", "[getOxyLoss()]") - stat("Toxin Levels:", "[getToxLoss()]") - stat("Burn Severity:", "[getFireLoss()]") - stat("Brute Trauma:", "[getBruteLoss()]") - stat("Radiation Levels:","[radiation] rad") - stat("Body Temperature:","[bodytemperature-T0C] degrees C ([bodytemperature*1.8-459.67] degrees F)") - - //Diseases - if(diseases.len) - stat("Viruses:", null) - for(var/thing in diseases) - var/datum/disease/D = thing - stat("*", "[D.name], Type: [D.spread_text], Stage: [D.stage]/[D.max_stages], Possible Cure: [D.cure_text]") - - -/mob/living/carbon/human/show_inv(mob/user) - user.set_machine(src) - var/has_breathable_mask = istype(wear_mask, /obj/item/clothing/mask) - var/list/obscured = check_obscured_slots() - var/list/dat = list() - - dat += "

Name

Status

Location

Control

[Bot.hacked ? "(!)" : ""] [Bot.name] ([Bot.model])[bot_mode][get_area_name(Bot, TRUE)]InterfaceCall
" - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "" - dat += "" - - dat += "" - - dat += "" - - if(SLOT_WEAR_MASK in obscured) - dat += "" - else - dat += "" - - if(SLOT_NECK in obscured) - dat += "" - else - dat += "" - - if(SLOT_GLASSES in obscured) - dat += "" - else - dat += "" - - if(SLOT_EARS 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 += "" - else - dat += "" - dat += "" - dat += "" - - if(handcuffed) - dat += "" - if(legcuffed) - dat += "" - - dat += {"
[get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]
 
Back:[(back && !(back.item_flags & ABSTRACT)) ? back : "Empty"]" - if(has_breathable_mask && istype(back, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - - dat += "
 
Head:[(head && !(head.item_flags & ABSTRACT)) ? head : "Empty"]
Mask:Obscured
Mask:[(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Empty"]
Neck:Obscured
Neck:[(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Empty"]
Eyes:Obscured
Eyes:[(glasses && !(glasses.item_flags & ABSTRACT)) ? glasses : "Empty"]
Ears:Obscured
Ears:[(ears && !(ears.item_flags & ABSTRACT)) ? ears : "Empty"]
 
Exosuit:[(wear_suit && !(wear_suit.item_flags & ABSTRACT)) ? wear_suit : "Empty"]
 ↳Suit Storage:[(s_store && !(s_store.item_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.item_flags & ABSTRACT)) ? shoes : "Empty"]
Gloves:Obscured
Gloves:[(gloves && !(gloves.item_flags & ABSTRACT)) ? gloves : "Empty"]
Uniform:Obscured
Uniform:[(w_uniform && !(w_uniform.item_flags & ABSTRACT)) ? w_uniform : "Empty"]
 ↳Pockets:
 ↳ID:
 ↳Belt:
 ↳Belt:[(belt && !(belt.item_flags & ABSTRACT)) ? belt : "Empty"]" - if(has_breathable_mask && istype(belt, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - dat += "
 ↳Pockets:[(l_store && !(l_store.item_flags & ABSTRACT)) ? "Left (Full)" : "Left (Empty)"]" - dat += " [(r_store && !(r_store.item_flags & ABSTRACT)) ? "Right (Full)" : "Right (Empty)"]
 ↳ID:[(wear_id && !(wear_id.item_flags & ABSTRACT)) ? wear_id : "Empty"]
Handcuffed: Remove
Legcuffed
- Close - "} - - var/datum/browser/popup = new(user, "mob[REF(src)]", "[src]", 440, 510) - popup.set_content(dat.Join()) - popup.open() - -// called when something steps onto a human -// this could be made more general, but for now just handle mulebot -/mob/living/carbon/human/Crossed(atom/movable/AM) - var/mob/living/simple_animal/bot/mulebot/MB = AM - if(istype(MB)) - MB.RunOver(src) - - //Hyper Change - Step on people - var/mob/living/carbon/human/H = AM - if(istype(H) && lying && H.a_intent != INTENT_HELP) - H.handle_micro_bump_other(src) - - spreadFire(AM) - -/mob/living/carbon/human/Topic(href, href_list) - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - if(href_list["embedded_object"]) - var/obj/item/bodypart/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.embedding.embedded_unsafe_removal_time/I.w_class //Citadel Change from * to / - 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 [DisplayTimeText(time_taken)].)") - if(do_after(usr, time_taken, needhand = 1, target = src)) - remove_embedded_unsafe(L, I, usr) - /* CITADEL EDIT: remove_embedded_unsafe replaces this code - if(!I || !L || I.loc != src || !(I in L.embedded_objects)) - return - L.embedded_objects -= I - L.receive_damage(I.embedding.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") - SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") */ - 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_held_item() // Item to place in the pocket, if it's empty - - var/delay_denominator = 1 - if(pocket_item && !(pocket_item.item_flags & ABSTRACT)) - if(HAS_TRAIT(pocket_item, TRAIT_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, usr, pocket_id, 1) && !(place_item.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, ignorehelditem = TRUE)) //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 - dropItemToGround(pocket_item) - if(!usr.can_hold_items() || !usr.put_in_hands(pocket_item)) - pocket_item.forceMove(drop_location()) - else - if(place_item) - if(place_item.mob_can_equip(src, usr, pocket_id, FALSE, TRUE)) - usr.temporarilyRemoveItemFromInventory(place_item, TRUE) - equip_to_slot(place_item, pocket_id, TRUE) - //do nothing otherwise - - // Update strip window - if(usr.machine == src && in_range(src, usr)) - show_inv(usr) - else - // Display a warning if the user mocks up - to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") - - ..() //CITADEL CHANGE - removes a tab from behind this ..() so that flavortext can actually be examined - - -///////HUDs/////// - if(href_list["hud"]) - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/perpname = get_face_name(get_id_name("")) - if(istype(H.glasses, /obj/item/clothing/glasses/hud) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud)) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general) - if(href_list["photo_front"] || href_list["photo_side"]) - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - return - var/obj/item/photo/P = null - if(href_list["photo_front"]) - P = R.fields["photo_front"] - else if(href_list["photo_side"]) - P = R.fields["photo_side"] - if(P) - P.show(H) - - if(href_list["hud"] == "m") - if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - if(href_list["p_stat"]) - var/health_status = input(usr, "Specify a new physical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("Active", "Physically Unfit", "*Unconscious*", "*Deceased*", "Cancel") - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - return - if(health_status && health_status != "Cancel") - R.fields["p_stat"] = health_status - return - if(href_list["m_stat"]) - var/health_status = input(usr, "Specify a new mental status for this person.", "Medical HUD", R.fields["m_stat"]) in list("Stable", "*Watch*", "*Unstable*", "*Insane*", "Cancel") - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - return - if(health_status && health_status != "Cancel") - R.fields["m_stat"] = health_status - return - if(href_list["evaluation"]) - if(!getBruteLoss() && !getFireLoss() && !getOxyLoss() && getToxLoss() < 20) - to_chat(usr, "No external injuries detected.
") - return - var/span = "notice" - var/status = "" - if(getBruteLoss()) - to_chat(usr, "Physical trauma analysis:") - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - var/brutedamage = BP.brute_dam - if(brutedamage > 0) - status = "received minor physical injuries." - span = "notice" - if(brutedamage > 20) - status = "been seriously damaged." - span = "danger" - if(brutedamage > 40) - status = "sustained major trauma!" - span = "userdanger" - if(brutedamage) - to_chat(usr, "[BP] appears to have [status]") - if(getFireLoss()) - to_chat(usr, "Analysis of skin burns:") - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - var/burndamage = BP.burn_dam - if(burndamage > 0) - status = "signs of minor burns." - span = "notice" - if(burndamage > 20) - status = "serious burns." - span = "danger" - if(burndamage > 40) - status = "major burns!" - span = "userdanger" - if(burndamage) - to_chat(usr, "[BP] appears to have [status]") - if(getOxyLoss()) - to_chat(usr, "Patient has signs of suffocation, emergency treatment may be required!") - if(getToxLoss() > 20) - to_chat(usr, "Gathered data is inconsistent with the analysis, possible cause: poisoning.") - - if(href_list["hud"] == "s") - if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - if(usr.stat || usr == src) //|| !usr.canmove || usr.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at. - return //Non-fluff: This allows sec to set people to arrest as they get disarmed or beaten - // Checks the user has security clearence before allowing them to change arrest status via hud, comment out to enable all access - var/allowed_access = null - var/obj/item/clothing/glasses/G = H.glasses - if (!(G.obj_flags |= EMAGGED)) - if(H.wear_id) - var/list/access = H.wear_id.GetAccess() - if(ACCESS_SEC_DOORS in access) - allowed_access = H.get_authentification_name() - else - allowed_access = "@%&ERROR_%$*" - - - if(!allowed_access) - to_chat(H, "ERROR: Invalid Access") - return - - if(perpname) - R = find_record("name", perpname, GLOB.data_core.security) - if(R) - if(href_list["status"]) - var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged", "Cancel") - if(setcriminal != "Cancel") - if(R) - if(H.canUseHUD()) - if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - investigate_log("[key_name(src)] has been set from [R.fields["criminal"]] to [setcriminal] by [key_name(usr)].", INVESTIGATE_RECORDS) - R.fields["criminal"] = setcriminal - sec_hud_set_security_status() - return - - if(href_list["view"]) - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]") - to_chat(usr, "Minor Crimes:") - for(var/datum/data/crime/c in R.fields["mi_crim"]) - to_chat(usr, "Crime: [c.crimeName]") - to_chat(usr, "Details: [c.crimeDetails]") - to_chat(usr, "Added by [c.author] at [c.time]") - to_chat(usr, "----------") - to_chat(usr, "Major Crimes:") - for(var/datum/data/crime/c in R.fields["ma_crim"]) - to_chat(usr, "Crime: [c.crimeName]") - to_chat(usr, "Details: [c.crimeDetails]") - to_chat(usr, "Added by [c.author] at [c.time]") - to_chat(usr, "----------") - to_chat(usr, "Notes: [R.fields["notes"]]") - return - - if(href_list["add_crime"]) - switch(alert("What crime would you like to add?","Security HUD","Minor Crime","Major Crime","Cancel")) - if("Minor Crime") - if(R) - var/t1 = stripped_input("Please input minor crime names:", "Security HUD", "", null) - var/t2 = stripped_multiline_input("Please input minor crime details:", "Security HUD", "", null) - if(R) - if (!t1 || !t2 || !allowed_access) - return - else if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) - GLOB.data_core.addMinorCrime(R.fields["id"], crime) - investigate_log("New Minor Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) - to_chat(usr, "Successfully added a minor crime.") - return - if("Major Crime") - if(R) - var/t1 = stripped_input("Please input major crime names:", "Security HUD", "", null) - var/t2 = stripped_multiline_input("Please input major crime details:", "Security HUD", "", null) - if(R) - if (!t1 || !t2 || !allowed_access) - return - else if (!H.canUseHUD()) - return - else if (!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) - GLOB.data_core.addMajorCrime(R.fields["id"], crime) - investigate_log("New Major Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) - to_chat(usr, "Successfully added a major crime.") - return - - if(href_list["view_comment"]) - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - to_chat(usr, "Comments/Log:") - var/counter = 1 - while(R.fields[text("com_[]", counter)]) - to_chat(usr, R.fields[text("com_[]", counter)]) - to_chat(usr, "----------") - counter++ - return - - if(href_list["add_comment"]) - if(R) - var/t1 = stripped_multiline_input("Add Comment:", "Secure. records", null, null) - if(R) - if (!t1 || !allowed_access) - return - else if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - var/counter = 1 - while(R.fields[text("com_[]", counter)]) - counter++ - R.fields[text("com_[]", counter)] = text("Made by [] on [] [], []
[]", allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) - to_chat(usr, "Successfully added comment.") - return - to_chat(usr, "Unable to locate a data core entry for this person.") - -/mob/living/carbon/human/proc/canUseHUD() - return !(src.stat || IsKnockdown() || IsStun() || src.restrained()) - -/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE, bypass_immunity = FALSE) - . = 1 // Default to returning true. - if(user && !target_zone) - target_zone = user.zone_selected - if(HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && !bypass_immunity) - . = 0 - // If targeting the head, see if the head item is thin enough. - // If targeting anything else, see if the wear suit is thin enough. - if (!penetrate_thick) - if(above_neck(target_zone)) - if(head && istype(head, /obj/item/clothing)) - var/obj/item/clothing/CH = head - if (CH.clothing_flags & THICKMATERIAL) - . = 0 - else - if(wear_suit && istype(wear_suit, /obj/item/clothing)) - var/obj/item/clothing/CS = wear_suit - if (CS.clothing_flags & THICKMATERIAL) - . = 0 - if(!. && error_msg && user) - // Might need re-wording. - to_chat(user, "There is no exposed flesh or thin material [above_neck(target_zone) ? "on [p_their()] head" : "on [p_their()] body"].") - -/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_EARS - - if(wear_mask) - if(wear_mask.flags_inv & HIDEEYES) - obscured |= SLOT_GLASSES - - if(obscured.len) - return obscured - else - return null - -/mob/living/carbon/human/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) - if(judgement_criteria & JUDGE_EMAGGED) - 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(is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) - threatcount += 4 - if(istype(belt, /obj/item/gun/energy/laser/redtag)) - threatcount += 2 - - if(lasercolor == "r") - if(istype(wear_suit, /obj/item/clothing/suit/bluetag)) - threatcount += 4 - if(is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) - threatcount += 4 - if(istype(belt, /obj/item/gun/energy/laser/bluetag)) - threatcount += 2 - - return threatcount - - //Check for ID - var/obj/item/card/id/idcard = get_idcard(FALSE) - if( (judgement_criteria & JUDGE_IDCHECK) && !idcard && name=="Unknown") - threatcount += 4 - - //Check for weapons - if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck) - if(!idcard || !(ACCESS_WEAPONS in idcard.access)) - for(var/obj/item/I in held_items) //if they're holding a gun - if(weaponcheck.Invoke(I)) - threatcount += 4 - if(weaponcheck.Invoke(belt) || weaponcheck.Invoke(back)) //if a weapon is present in the belt or back slot - threatcount += 2 //not enough to trigger look_for_perp() on it's own unless they also have criminal status. - - //Check for arrest warrant - if(judgement_criteria & JUDGE_RECORDCHECK) - var/perpname = get_face_name(get_id_name()) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) - if(R && R.fields["criminal"]) - switch(R.fields["criminal"]) - if("*Arrest*") - threatcount += 5 - if("Incarcerated") - threatcount += 2 - if("Paroled") - threatcount += 2 - - //Check for dresscode violations - if(istype(head, /obj/item/clothing/head/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/syndi) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi)) - threatcount += 4 //fuk u antags <3 //no you - - //mindshield implants imply trustworthyness - if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) - threatcount -= 1 - - //Agent cards lower threatlevel. - if(istype(idcard, /obj/item/card/id/syndicate)) - threatcount -= 2 - - return threatcount - - -//Used for new human mobs created by cloning/goleming/podding -/mob/living/carbon/human/proc/set_cloned_appearance() - if(gender == MALE) - facial_hair_style = "Full Beard" - else - facial_hair_style = "Shaved" - hair_style = pick("Bedhead", "Bedhead 2", "Bedhead 3") - underwear = "Nude" - undershirt = "Nude" - update_body() - update_hair() - update_genitals() - -/mob/living/carbon/human/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_THREE) - for(var/obj/item/hand in held_items) - if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && dropItemToGround(hand)) - step_towards(hand, src) - to_chat(src, "\The [S] pulls \the [hand] from your grip!") - rad_act(current_size * 3) - if(mob_negates_gravity()) - return - -/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/C) - CHECK_DNA_AND_SPECIES(C) - - if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - to_chat(src, "[C.name] is dead!") - return - if(is_mouth_covered()) - to_chat(src, "Remove your mask first!") - return 0 - if(C.is_mouth_covered()) - to_chat(src, "Remove [p_their()] mask first!") - return 0 - - if(C.cpr_time < world.time + 30) - visible_message("[src] is trying to perform CPR on [C.name]!", \ - "You try to perform CPR on [C.name]... Hold still!") - if(!do_mob(src, C)) - to_chat(src, "You fail to perform CPR on [C]!") - return 0 - - var/they_breathe = !HAS_TRAIT(C, TRAIT_NOBREATH) - var/they_lung = C.getorganslot(ORGAN_SLOT_LUNGS) - - if(C.health > C.crit_threshold) - return - - src.visible_message("[src] performs CPR on [C.name]!", "You perform CPR on [C.name].") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "perform_cpr", /datum/mood_event/perform_cpr) - C.cpr_time = world.time - log_combat(src, C, "CPRed") - - if(they_breathe && they_lung) - var/suff = min(C.getOxyLoss(), 7) - C.adjustOxyLoss(-suff) - C.updatehealth() - to_chat(C, "You feel a breath of fresh air enter your lungs... It feels good...") - else if(they_breathe && !they_lung) - to_chat(C, "You feel a breath of fresh air... but you don't feel any better...") - else - to_chat(C, "You feel a breath of fresh air... which is a sensation you don't recognise...") - -/mob/living/carbon/human/cuff_resist(obj/item/I) - if(dna && dna.check_mutation(HULK)) - say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") - if(..(I, cuff_break = FAST_CUFFBREAK)) - dropItemToGround(I) - else - if(..()) - dropItemToGround(I) - -/mob/living/carbon/human/clean_blood() - var/mob/living/carbon/human/H = src - if(H.gloves) - if(H.gloves.clean_blood()) - H.update_inv_gloves() - else - ..() // Clear the Blood_DNA list - if(H.bloody_hands) - H.bloody_hands = 0 - H.update_inv_gloves() - update_icons() //apply the now updated overlays to the mob - -/mob/living/carbon/human/wash_cream() - if(creamed) //clean both to prevent a rare bug - cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_lizard")) - cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_human")) - cut_overlay(mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi', "cum_normal")) - cut_overlay(mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi', "cum_large")) - creamed = FALSE - - -//Turns a mob black, flashes a skeleton overlay -//Just like a cartoon! -/mob/living/carbon/human/proc/electrocution_animation(anim_duration) - //Handle mutant parts if possible - if(dna && dna.species) - add_atom_colour("#000000", TEMPORARY_COLOUR_PRIORITY) - var/static/mutable_appearance/electrocution_skeleton_anim - if(!electrocution_skeleton_anim) - electrocution_skeleton_anim = mutable_appearance(icon, "electrocuted_base") - electrocution_skeleton_anim.appearance_flags |= RESET_COLOR|KEEP_APART - add_overlay(electrocution_skeleton_anim) - addtimer(CALLBACK(src, .proc/end_electrocution_animation, electrocution_skeleton_anim), anim_duration) - - else //or just do a generic animation - flick_overlay_view(image(icon,src,"electrocuted_generic",ABOVE_MOB_LAYER), src, anim_duration) - -/mob/living/carbon/human/proc/end_electrocution_animation(mutable_appearance/MA) - remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#000000") - cut_overlay(MA) - -/mob/living/carbon/human/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) - if(incapacitated() || lying ) - to_chat(src, "You can't do that right now!") - return FALSE - if(!Adjacent(M) && (M.loc != src)) - if((be_close == 0) || (!no_tk && (dna.check_mutation(TK) && tkMaxRangeCheck(src, M)))) - return TRUE - to_chat(src, "You are too far away!") - return FALSE - return TRUE - -/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/replace_records_name(oldname,newname) // Only humans have records right now, move this up if changed. - for(var/list/L in list(GLOB.data_core.general,GLOB.data_core.medical,GLOB.data_core.security,GLOB.data_core.locked)) - var/datum/data/record/R = find_record("name", oldname, L) - if(R) - R.fields["name"] = newname - -/mob/living/carbon/human/get_total_tint() - . = ..() - if(glasses) - . += glasses.tint - -/mob/living/carbon/human/update_health_hud() - if(!client || !hud_used) - return - if(dna.species.update_health_hud()) - return - else - if(hud_used.healths) - var/health_amount = health - CLAMP(getStaminaLoss()-50, 0, 80)//CIT CHANGE - makes staminaloss have less of an impact on the health hud - if(..(health_amount)) //not dead - switch(hal_screwyhud) - if(SCREWYHUD_CRIT) - hud_used.healths.icon_state = "health6" - if(SCREWYHUD_DEAD) - hud_used.healths.icon_state = "health7" - if(SCREWYHUD_HEALTHY) - hud_used.healths.icon_state = "health0" - if(hud_used.healthdoll) - hud_used.healthdoll.cut_overlays() - if(stat != DEAD) - hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - var/damage = BP.burn_dam + BP.brute_dam - var/comparison = (BP.max_damage/5) - var/icon_num = 0 - if(damage) - icon_num = 1 - if(damage > (comparison)) - icon_num = 2 - if(damage > (comparison*2)) - icon_num = 3 - if(damage > (comparison*3)) - icon_num = 4 - if(damage > (comparison*4)) - icon_num = 5 - if(hal_screwyhud == SCREWYHUD_HEALTHY) - icon_num = 0 - if(icon_num) - hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[BP.body_zone][icon_num]")) - for(var/t in get_missing_limbs()) //Missing limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]6")) - for(var/t in get_disabled_limbs()) //Disabled limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]7")) - else - hud_used.healthdoll.icon_state = "healthdoll_DEAD" - -/mob/living/carbon/human/fully_heal(admin_revive = 0) - if(admin_revive) - regenerate_limbs() - regenerate_organs() - remove_all_embedded_objects() - set_heartattack(FALSE) - drunkenness = 0 - for(var/datum/mutation/human/HM in dna.mutations) - if(HM.quality != POSITIVE) - dna.remove_mutation(HM.name) - if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio)) - blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio) - ..() - -/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker) - . = ..() - if (dna && dna.species) - . += dna.species.check_weakness(weapon, attacker) - -/mob/living/carbon/human/is_literate() - return 1 - -/mob/living/carbon/human/can_hold_items() - return TRUE - -/mob/living/carbon/human/update_gravity(has_gravity,override = 0) - if(dna && dna.species) //prevents a runtime while a human is being monkeyfied - override = dna.species.override_float - ..() - -/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = 0, stun = 1, distance = 0, message = 1, toxic = 0) - if(blood && (NOBLOOD in dna.species.species_traits)) - if(message) - visible_message("[src] dry heaves!", \ - "You try to throw up, but there's nothing in your stomach!") - if(stun) - Knockdown(200) - return 1 - ..() - -/mob/living/carbon/human/vv_get_dropdown() - . = ..() - . += "---" - .["Make monkey"] = "?_src_=vars;[HrefToken()];makemonkey=[REF(src)]" - .["Set Species"] = "?_src_=vars;[HrefToken()];setspecies=[REF(src)]" - .["Make cyborg"] = "?_src_=vars;[HrefToken()];makerobot=[REF(src)]" - .["Make alien"] = "?_src_=vars;[HrefToken()];makealien=[REF(src)]" - .["Make slime"] = "?_src_=vars;[HrefToken()];makeslime=[REF(src)]" - .["Toggle Purrbation"] = "?_src_=vars;[HrefToken()];purrbation=[REF(src)]" - .["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]" - .["Add/Remove Quirks"] = "?_src_=vars;[HrefToken()];modquirks=[REF(src)]" - -/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) - if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) - //If they dragged themselves and we're currently aggressively grabbing them try to piggyback - if(user == target && can_piggyback(target)) - piggyback(target) - return - //If you dragged them to you and you're aggressively grabbing try to fireman carry them - else if(user != target) - fireman_carry(target) - return - . = ..() - -//src is the user that will be carrying, target is the mob to be carried -/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target) - return (istype(target) && target.stat == CONSCIOUS) - -/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target) - return (ishuman(target) && target.lying) - -/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target) - if(can_be_firemanned(target)) - visible_message("[src] starts lifting [target] onto their back...", - "You start lifting [target] onto your back...") - if(do_after(src, 30, TRUE, target)) - //Second check to make sure they're still valid to be carried - if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) - target.resting = FALSE - buckle_mob(target, TRUE, TRUE, 90, 1, 0) - return - visible_message("[src] fails to fireman carry [target]!") - else - to_chat(src, "You can't fireman carry [target] while they're standing!") - -/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target) - if(can_piggyback(target)) - visible_message("[target] starts to climb onto [src]...") - if(do_after(target, 15, target = src)) - if(can_piggyback(target)) - if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) - target.visible_message("[target] can't hang onto [src]!") - return - buckle_mob(target, TRUE, TRUE, FALSE, 0, 2) - else - visible_message("[target] fails to climb onto [src]!") - else - to_chat(target, "You can't piggyback ride [src] right now!") - -/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0) - if(!force)//humans are only meant to be ridden through piggybacking and special cases - return - if(!is_type_in_typecache(target, can_ride_typecache)) - target.visible_message("[target] really can't seem to mount [src]...") - return - buckle_lying = lying_buckle - var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human) - if(target_hands_needed) - riding_datum.ride_check_rider_restrained = TRUE - if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled) - return - var/equipped_hands_self - var/equipped_hands_target - if(hands_needed) - equipped_hands_self = riding_datum.equip_buckle_inhands(src, hands_needed, target) - if(target_hands_needed) - equipped_hands_target = riding_datum.equip_buckle_inhands(target, target_hands_needed) - - if(hands_needed || target_hands_needed) - if(hands_needed && !equipped_hands_self) - src.visible_message("[src] can't get a grip on [target] because their hands are full!", - "You can't get a grip on [target] because your hands are full!") - return - else if(target_hands_needed && !equipped_hands_target) - target.visible_message("[target] can't get a grip on [src] because their hands are full!", - "You can't get a grip on [src] because your hands are full!") - return - - stop_pulling() - riding_datum.handle_vehicle_layer() - . = ..(target, force, check_loc) - -/mob/living/carbon/human/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this - for(var/obj/item/clothing/C in get_equipped_items()) //doesn't include pockets - if(C.blocks_shove_knockdown) - return TRUE - return FALSE - -/mob/living/carbon/human/proc/clear_shove_slowdown() - remove_movespeed_modifier(SHOVE_SLOWDOWN_ID) - var/active_item = get_active_held_item() - if(is_type_in_typecache(active_item, GLOB.shove_disarming_types)) - visible_message("[src.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE) - -/mob/living/carbon/human/do_after_coefficent() - . = ..() - . *= physiology.do_after_speed - -/mob/living/carbon/human/species - var/race = null - -/mob/living/carbon/human/species/Initialize() - . = ..() - set_species(race) - -/mob/living/carbon/human/species/abductor - race = /datum/species/abductor - -/mob/living/carbon/human/species/android - race = /datum/species/android - -/mob/living/carbon/human/species/angel - race = /datum/species/angel - -/mob/living/carbon/human/species/corporate - race = /datum/species/corporate - -/mob/living/carbon/human/species/dullahan - race = /datum/species/dullahan - -/mob/living/carbon/human/species/felinid - race = /datum/species/human/felinid - -/mob/living/carbon/human/species/fly - race = /datum/species/fly - -/mob/living/carbon/human/species/golem - race = /datum/species/golem - -/mob/living/carbon/human/species/golem/random - race = /datum/species/golem/random - -/mob/living/carbon/human/species/golem/adamantine - race = /datum/species/golem/adamantine - -/mob/living/carbon/human/species/golem/plasma - race = /datum/species/golem/plasma - -/mob/living/carbon/human/species/golem/diamond - race = /datum/species/golem/diamond - -/mob/living/carbon/human/species/golem/gold - race = /datum/species/golem/gold - -/mob/living/carbon/human/species/golem/silver - race = /datum/species/golem/silver - -/mob/living/carbon/human/species/golem/plasteel - race = /datum/species/golem/plasteel - -/mob/living/carbon/human/species/golem/titanium - race = /datum/species/golem/titanium - -/mob/living/carbon/human/species/golem/plastitanium - race = /datum/species/golem/plastitanium - -/mob/living/carbon/human/species/golem/alien_alloy - race = /datum/species/golem/alloy - -/mob/living/carbon/human/species/golem/wood - race = /datum/species/golem/wood - -/mob/living/carbon/human/species/golem/uranium - race = /datum/species/golem/uranium - -/mob/living/carbon/human/species/golem/sand - race = /datum/species/golem/sand - -/mob/living/carbon/human/species/golem/glass - race = /datum/species/golem/glass - -/mob/living/carbon/human/species/golem/bluespace - race = /datum/species/golem/bluespace - -/mob/living/carbon/human/species/golem/bananium - race = /datum/species/golem/bananium - -/mob/living/carbon/human/species/golem/blood_cult - race = /datum/species/golem/runic - -/mob/living/carbon/human/species/golem/cloth - race = /datum/species/golem/cloth - -/mob/living/carbon/human/species/golem/plastic - race = /datum/species/golem/plastic - -/mob/living/carbon/human/species/golem/clockwork - race = /datum/species/golem/clockwork - -/mob/living/carbon/human/species/golem/clockwork/no_scrap - race = /datum/species/golem/clockwork/no_scrap - -/mob/living/carbon/human/species/jelly - race = /datum/species/jelly - -/mob/living/carbon/human/species/jelly/slime - race = /datum/species/jelly/slime - -/mob/living/carbon/human/species/jelly/stargazer - race = /datum/species/jelly/stargazer - -/mob/living/carbon/human/species/jelly/luminescent - race = /datum/species/jelly/luminescent - -/mob/living/carbon/human/species/lizard - race = /datum/species/lizard - -/mob/living/carbon/human/species/lizard/ashwalker - race = /datum/species/lizard/ashwalker - -/mob/living/carbon/human/species/moth - race = /datum/species/moth - -/mob/living/carbon/human/species/mush - race = /datum/species/mush - -/mob/living/carbon/human/species/plasma - race = /datum/species/plasmaman - -/mob/living/carbon/human/species/pod - race = /datum/species/pod - -/mob/living/carbon/human/species/shadow - race = /datum/species/shadow - -/mob/living/carbon/human/species/shadow/nightmare - race = /datum/species/shadow/nightmare - -/mob/living/carbon/human/species/skeleton - race = /datum/species/skeleton - -/mob/living/carbon/human/species/synth - race = /datum/species/synth - -/mob/living/carbon/human/species/synth/military - race = /datum/species/synth/military - -/mob/living/carbon/human/species/vampire - race = /datum/species/vampire - -/mob/living/carbon/human/species/zombie - race = /datum/species/zombie - -/mob/living/carbon/human/species/zombie/infectious - race = /datum/species/zombie/infectious - -/mob/living/carbon/human/species/zombie/krokodil_addict - race = /datum/species/krokodil_addict - -/mob/living/carbon/human/species/mammal - race = /datum/species/mammal - -/mob/living/carbon/human/species/insect - race = /datum/species/insect - -/mob/living/carbon/human/species/xeno - race = /datum/species/xeno - -/mob/living/carbon/human/species/ipc - race = /datum/species/ipc - -/mob/living/carbon/human/species/roundstartslime - race = /datum/species/jelly/roundstartslime -//define holder_type on nerds we wanna commit scoop to -/* /mob/living/carbon/human - var/holder_type = /obj/item/clothing/head/mob_holder/micro - can_be_held = "micro" -*/ - +/mob/living/carbon/human + name = "Unknown" + real_name = "Unknown" + icon = 'icons/mob/human.dmi' + icon_state = "caucasian_m" + appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE + +/mob/living/carbon/human/Initialize() + verbs += /mob/living/proc/mob_sleep + verbs += /mob/living/proc/lay_down + verbs += /mob/living/carbon/human/proc/underwear_toggle //fwee + time_initialized = world.time + + //initialize limbs first + create_bodyparts() + + //initialize dna. for spawned humans; overwritten by other code + create_dna(src) + randomize_human(src) + dna.initialize_dna() + + if(dna.species) + set_species(dna.species.type) + + //initialise organs + create_internal_organs() //most of it is done in set_species now, this is only for parent call + physiology = new() + + handcrafting = new() + + . = ..() + + if(CONFIG_GET(flag/disable_stambuffer)) + togglesprint() + + AddComponent(/datum/component/redirect, list(COMSIG_COMPONENT_CLEAN_ACT = CALLBACK(src, /mob/living/carbon/human/clean_blood))) + + +/mob/living/carbon/human/ComponentInitialize() + . = ..() + if(!CONFIG_GET(flag/disable_human_mood)) + AddComponent(/datum/component/mood) + AddElement(/datum/element/mob_holder/micro, "micro") + +/mob/living/carbon/human/Destroy() + QDEL_NULL(physiology) + QDEL_NULL_LIST(vore_organs) // CITADEL EDIT belly stuff + return ..() + + +/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/Stat() + ..() + //Same thing from mob + if(statpanel("Status")) + if(tickrefresh == 1) + sList2 = list() + sList2 += "Intent: [a_intent]" + sList2 += "Move Mode: [m_intent]" + if (internal) + if (!internal.air_contents) + qdel(internal) + else + sList2 += "Internal Atmosphere Info: "+ "[internal.name]" + sList2 += "Tank Pressure: "+ "[internal.air_contents.return_pressure()]" + sList2 += "Distribution Pressure: "+ "[internal.distribute_pressure]" + if(mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + sList2 += "Chemical Storage: " + "[changeling.chem_charges]/[changeling.chem_storage]" + sList2 += "Absorbed DNA: "+ "[changeling.absorbedcount]" + if (sList2 != null) + stat(null, "[sList2.Join("\n\n")]") + + //NINJACODE + if(istype(wear_suit, /obj/item/clothing/suit/space/space_ninja)) //Only display if actually a ninja. + var/obj/item/clothing/suit/space/space_ninja/SN = wear_suit + if(statpanel("SpiderOS")) + stat("SpiderOS Status:","[SN.s_initialized ? "Initialized" : "Disabled"]") + stat("Current Time:", "[STATION_TIME_TIMESTAMP("hh:mm:ss")]") + if(SN.s_initialized) + //Suit gear + stat("Energy Charge:", "[round(SN.cell.charge/100)]%") + stat("Smoke Bombs:", "\Roman [SN.s_bombs]") + //Ninja status + stat("Fingerprints:", "[md5(dna.uni_identity)]") + stat("Unique Identity:", "[dna.unique_enzymes]") + stat("Overall Status:", "[stat > 1 ? "dead" : "[health]% healthy"]") + stat("Nutrition Status:", "[nutrition]") + stat("Oxygen Loss:", "[getOxyLoss()]") + stat("Toxin Levels:", "[getToxLoss()]") + stat("Burn Severity:", "[getFireLoss()]") + stat("Brute Trauma:", "[getBruteLoss()]") + stat("Radiation Levels:","[radiation] rad") + stat("Body Temperature:","[bodytemperature-T0C] degrees C ([bodytemperature*1.8-459.67] degrees F)") + + //Diseases + if(diseases.len) + stat("Viruses:", null) + for(var/thing in diseases) + var/datum/disease/D = thing + stat("*", "[D.name], Type: [D.spread_text], Stage: [D.stage]/[D.max_stages], Possible Cure: [D.cure_text]") + + +/mob/living/carbon/human/show_inv(mob/user) + user.set_machine(src) + var/has_breathable_mask = istype(wear_mask, /obj/item/clothing/mask) + var/list/obscured = check_obscured_slots() + var/list/dat = list() + + dat += "" + for(var/i in 1 to held_items.len) + var/obj/item/I = get_item_for_held_index(i) + dat += "" + dat += "" + + dat += "" + + dat += "" + + if(SLOT_WEAR_MASK in obscured) + dat += "" + else + dat += "" + + if(SLOT_NECK in obscured) + dat += "" + else + dat += "" + + if(SLOT_GLASSES in obscured) + dat += "" + else + dat += "" + + if(SLOT_EARS 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 += "" + else + dat += "" + dat += "" + dat += "" + + if(handcuffed) + dat += "" + if(legcuffed) + dat += "" + + dat += {"
[get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]
 
Back:[(back && !(back.item_flags & ABSTRACT)) ? back : "Empty"]" + if(has_breathable_mask && istype(back, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + + dat += "
 
Head:[(head && !(head.item_flags & ABSTRACT)) ? head : "Empty"]
Mask:Obscured
Mask:[(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Empty"]
Neck:Obscured
Neck:[(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Empty"]
Eyes:Obscured
Eyes:[(glasses && !(glasses.item_flags & ABSTRACT)) ? glasses : "Empty"]
Ears:Obscured
Ears:[(ears && !(ears.item_flags & ABSTRACT)) ? ears : "Empty"]
 
Exosuit:[(wear_suit && !(wear_suit.item_flags & ABSTRACT)) ? wear_suit : "Empty"]
 ↳Suit Storage:[(s_store && !(s_store.item_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.item_flags & ABSTRACT)) ? shoes : "Empty"]
Gloves:Obscured
Gloves:[(gloves && !(gloves.item_flags & ABSTRACT)) ? gloves : "Empty"]
Uniform:Obscured
Uniform:[(w_uniform && !(w_uniform.item_flags & ABSTRACT)) ? w_uniform : "Empty"]
 ↳Pockets:
 ↳ID:
 ↳Belt:
 ↳Belt:[(belt && !(belt.item_flags & ABSTRACT)) ? belt : "Empty"]" + if(has_breathable_mask && istype(belt, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + dat += "
 ↳Pockets:[(l_store && !(l_store.item_flags & ABSTRACT)) ? "Left (Full)" : "Left (Empty)"]" + dat += " [(r_store && !(r_store.item_flags & ABSTRACT)) ? "Right (Full)" : "Right (Empty)"]
 ↳ID:[(wear_id && !(wear_id.item_flags & ABSTRACT)) ? wear_id : "Empty"]
Handcuffed: Remove
Legcuffed
+ Close + "} + + var/datum/browser/popup = new(user, "mob[REF(src)]", "[src]", 440, 510) + popup.set_content(dat.Join()) + popup.open() + +// called when something steps onto a human +// this could be made more general, but for now just handle mulebot +/mob/living/carbon/human/Crossed(atom/movable/AM) + var/mob/living/simple_animal/bot/mulebot/MB = AM + if(istype(MB)) + MB.RunOver(src) + + //Hyper Change - Step on people + var/mob/living/carbon/human/H = AM + if(istype(H) && lying && H.a_intent != INTENT_HELP) + H.handle_micro_bump_other(src) + + spreadFire(AM) + +/mob/living/carbon/human/Topic(href, href_list) + if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + if(href_list["embedded_object"]) + var/obj/item/bodypart/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.embedding.embedded_unsafe_removal_time/I.w_class //Citadel Change from * to / + 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 [DisplayTimeText(time_taken)].)") + if(do_after(usr, time_taken, needhand = 1, target = src)) + remove_embedded_unsafe(L, I, usr) + /* CITADEL EDIT: remove_embedded_unsafe replaces this code + if(!I || !L || I.loc != src || !(I in L.embedded_objects)) + return + L.embedded_objects -= I + L.receive_damage(I.embedding.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") + SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") */ + 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_held_item() // Item to place in the pocket, if it's empty + + var/delay_denominator = 1 + if(pocket_item && !(pocket_item.item_flags & ABSTRACT)) + if(HAS_TRAIT(pocket_item, TRAIT_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, usr, pocket_id, 1) && !(place_item.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, ignorehelditem = TRUE)) //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 + dropItemToGround(pocket_item) + if(!usr.can_hold_items() || !usr.put_in_hands(pocket_item)) + pocket_item.forceMove(drop_location()) + else + if(place_item) + if(place_item.mob_can_equip(src, usr, pocket_id, FALSE, TRUE)) + usr.temporarilyRemoveItemFromInventory(place_item, TRUE) + equip_to_slot(place_item, pocket_id, TRUE) + //do nothing otherwise + + // Update strip window + if(usr.machine == src && in_range(src, usr)) + show_inv(usr) + else + // Display a warning if the user mocks up + to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") + + ..() //CITADEL CHANGE - removes a tab from behind this ..() so that flavortext can actually be examined + + +///////HUDs/////// + if(href_list["hud"]) + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/perpname = get_face_name(get_id_name("")) + if(istype(H.glasses, /obj/item/clothing/glasses/hud) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud)) + var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general) + if(href_list["photo_front"] || href_list["photo_side"]) + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + return + var/obj/item/photo/P = null + if(href_list["photo_front"]) + P = R.fields["photo_front"] + else if(href_list["photo_side"]) + P = R.fields["photo_side"] + if(P) + P.show(H) + + if(href_list["hud"] == "m") + if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + if(href_list["p_stat"]) + var/health_status = input(usr, "Specify a new physical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("Active", "Physically Unfit", "*Unconscious*", "*Deceased*", "Cancel") + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + return + if(health_status && health_status != "Cancel") + R.fields["p_stat"] = health_status + return + if(href_list["m_stat"]) + var/health_status = input(usr, "Specify a new mental status for this person.", "Medical HUD", R.fields["m_stat"]) in list("Stable", "*Watch*", "*Unstable*", "*Insane*", "Cancel") + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + return + if(health_status && health_status != "Cancel") + R.fields["m_stat"] = health_status + return + if(href_list["evaluation"]) + if(!getBruteLoss() && !getFireLoss() && !getOxyLoss() && getToxLoss() < 20) + to_chat(usr, "No external injuries detected.
") + return + var/span = "notice" + var/status = "" + if(getBruteLoss()) + to_chat(usr, "Physical trauma analysis:") + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + var/brutedamage = BP.brute_dam + if(brutedamage > 0) + status = "received minor physical injuries." + span = "notice" + if(brutedamage > 20) + status = "been seriously damaged." + span = "danger" + if(brutedamage > 40) + status = "sustained major trauma!" + span = "userdanger" + if(brutedamage) + to_chat(usr, "[BP] appears to have [status]") + if(getFireLoss()) + to_chat(usr, "Analysis of skin burns:") + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + var/burndamage = BP.burn_dam + if(burndamage > 0) + status = "signs of minor burns." + span = "notice" + if(burndamage > 20) + status = "serious burns." + span = "danger" + if(burndamage > 40) + status = "major burns!" + span = "userdanger" + if(burndamage) + to_chat(usr, "[BP] appears to have [status]") + if(getOxyLoss()) + to_chat(usr, "Patient has signs of suffocation, emergency treatment may be required!") + if(getToxLoss() > 20) + to_chat(usr, "Gathered data is inconsistent with the analysis, possible cause: poisoning.") + + if(href_list["hud"] == "s") + if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + if(usr.stat || usr == src) //|| !usr.canmove || usr.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at. + return //Non-fluff: This allows sec to set people to arrest as they get disarmed or beaten + // Checks the user has security clearence before allowing them to change arrest status via hud, comment out to enable all access + var/allowed_access = null + var/obj/item/clothing/glasses/G = H.glasses + if (!(G.obj_flags |= EMAGGED)) + if(H.wear_id) + var/list/access = H.wear_id.GetAccess() + if(ACCESS_SEC_DOORS in access) + allowed_access = H.get_authentification_name() + else + allowed_access = "@%&ERROR_%$*" + + + if(!allowed_access) + to_chat(H, "ERROR: Invalid Access") + return + + if(perpname) + R = find_record("name", perpname, GLOB.data_core.security) + if(R) + if(href_list["status"]) + var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged", "Cancel") + if(setcriminal != "Cancel") + if(R) + if(H.canUseHUD()) + if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + investigate_log("[key_name(src)] has been set from [R.fields["criminal"]] to [setcriminal] by [key_name(usr)].", INVESTIGATE_RECORDS) + R.fields["criminal"] = setcriminal + sec_hud_set_security_status() + return + + if(href_list["view"]) + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]") + to_chat(usr, "Minor Crimes:") + for(var/datum/data/crime/c in R.fields["mi_crim"]) + to_chat(usr, "Crime: [c.crimeName]") + to_chat(usr, "Details: [c.crimeDetails]") + to_chat(usr, "Added by [c.author] at [c.time]") + to_chat(usr, "----------") + to_chat(usr, "Major Crimes:") + for(var/datum/data/crime/c in R.fields["ma_crim"]) + to_chat(usr, "Crime: [c.crimeName]") + to_chat(usr, "Details: [c.crimeDetails]") + to_chat(usr, "Added by [c.author] at [c.time]") + to_chat(usr, "----------") + to_chat(usr, "Notes: [R.fields["notes"]]") + return + + if(href_list["add_crime"]) + switch(alert("What crime would you like to add?","Security HUD","Minor Crime","Major Crime","Cancel")) + if("Minor Crime") + if(R) + var/t1 = stripped_input("Please input minor crime names:", "Security HUD", "", null) + var/t2 = stripped_multiline_input("Please input minor crime details:", "Security HUD", "", null) + if(R) + if (!t1 || !t2 || !allowed_access) + return + else if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) + GLOB.data_core.addMinorCrime(R.fields["id"], crime) + investigate_log("New Minor Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) + to_chat(usr, "Successfully added a minor crime.") + return + if("Major Crime") + if(R) + var/t1 = stripped_input("Please input major crime names:", "Security HUD", "", null) + var/t2 = stripped_multiline_input("Please input major crime details:", "Security HUD", "", null) + if(R) + if (!t1 || !t2 || !allowed_access) + return + else if (!H.canUseHUD()) + return + else if (!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) + GLOB.data_core.addMajorCrime(R.fields["id"], crime) + investigate_log("New Major Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) + to_chat(usr, "Successfully added a major crime.") + return + + if(href_list["view_comment"]) + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + to_chat(usr, "Comments/Log:") + var/counter = 1 + while(R.fields[text("com_[]", counter)]) + to_chat(usr, R.fields[text("com_[]", counter)]) + to_chat(usr, "----------") + counter++ + return + + if(href_list["add_comment"]) + if(R) + var/t1 = stripped_multiline_input("Add Comment:", "Secure. records", null, null) + if(R) + if (!t1 || !allowed_access) + return + else if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + var/counter = 1 + while(R.fields[text("com_[]", counter)]) + counter++ + R.fields[text("com_[]", counter)] = text("Made by [] on [] [], []
[]", allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) + to_chat(usr, "Successfully added comment.") + return + to_chat(usr, "Unable to locate a data core entry for this person.") + +/mob/living/carbon/human/proc/canUseHUD() + return !(src.stat || IsKnockdown() || IsStun() || src.restrained()) + +/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE, bypass_immunity = FALSE) + . = 1 // Default to returning true. + if(user && !target_zone) + target_zone = user.zone_selected + if(HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && !bypass_immunity) + . = 0 + // If targeting the head, see if the head item is thin enough. + // If targeting anything else, see if the wear suit is thin enough. + if (!penetrate_thick) + if(above_neck(target_zone)) + if(head && istype(head, /obj/item/clothing)) + var/obj/item/clothing/CH = head + if (CH.clothing_flags & THICKMATERIAL) + . = 0 + else + if(wear_suit && istype(wear_suit, /obj/item/clothing)) + var/obj/item/clothing/CS = wear_suit + if (CS.clothing_flags & THICKMATERIAL) + . = 0 + if(!. && error_msg && user) + // Might need re-wording. + to_chat(user, "There is no exposed flesh or thin material [above_neck(target_zone) ? "on [p_their()] head" : "on [p_their()] body"].") + +/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_EARS + + if(wear_mask) + if(wear_mask.flags_inv & HIDEEYES) + obscured |= SLOT_GLASSES + + if(obscured.len) + return obscured + else + return null + +/mob/living/carbon/human/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) + if(judgement_criteria & JUDGE_EMAGGED) + 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(is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) + threatcount += 4 + if(istype(belt, /obj/item/gun/energy/laser/redtag)) + threatcount += 2 + + if(lasercolor == "r") + if(istype(wear_suit, /obj/item/clothing/suit/bluetag)) + threatcount += 4 + if(is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) + threatcount += 4 + if(istype(belt, /obj/item/gun/energy/laser/bluetag)) + threatcount += 2 + + return threatcount + + //Check for ID + var/obj/item/card/id/idcard = get_idcard(FALSE) + if( (judgement_criteria & JUDGE_IDCHECK) && !idcard && name=="Unknown") + threatcount += 4 + + //Check for weapons + if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck) + if(!idcard || !(ACCESS_WEAPONS in idcard.access)) + for(var/obj/item/I in held_items) //if they're holding a gun + if(weaponcheck.Invoke(I)) + threatcount += 4 + if(weaponcheck.Invoke(belt) || weaponcheck.Invoke(back)) //if a weapon is present in the belt or back slot + threatcount += 2 //not enough to trigger look_for_perp() on it's own unless they also have criminal status. + + //Check for arrest warrant + if(judgement_criteria & JUDGE_RECORDCHECK) + var/perpname = get_face_name(get_id_name()) + var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) + if(R && R.fields["criminal"]) + switch(R.fields["criminal"]) + if("*Arrest*") + threatcount += 5 + if("Incarcerated") + threatcount += 2 + if("Paroled") + threatcount += 2 + + //Check for dresscode violations + if(istype(head, /obj/item/clothing/head/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/syndi) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi)) + threatcount += 4 //fuk u antags <3 //no you + + //mindshield implants imply trustworthyness + if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) + threatcount -= 1 + + //Agent cards lower threatlevel. + if(istype(idcard, /obj/item/card/id/syndicate)) + threatcount -= 2 + + return threatcount + + +//Used for new human mobs created by cloning/goleming/podding +/mob/living/carbon/human/proc/set_cloned_appearance() + if(gender == MALE) + facial_hair_style = "Full Beard" + else + facial_hair_style = "Shaved" + hair_style = pick("Bedhead", "Bedhead 2", "Bedhead 3") + underwear = "Nude" + undershirt = "Nude" + update_body() + update_hair() + update_genitals() + +/mob/living/carbon/human/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_THREE) + for(var/obj/item/hand in held_items) + if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && dropItemToGround(hand)) + step_towards(hand, src) + to_chat(src, "\The [S] pulls \the [hand] from your grip!") + rad_act(current_size * 3) + if(mob_negates_gravity()) + return + +/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/C) + CHECK_DNA_AND_SPECIES(C) + + if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) + to_chat(src, "[C.name] is dead!") + return + if(is_mouth_covered()) + to_chat(src, "Remove your mask first!") + return 0 + if(C.is_mouth_covered()) + to_chat(src, "Remove [p_their()] mask first!") + return 0 + + if(C.cpr_time < world.time + 30) + visible_message("[src] is trying to perform CPR on [C.name]!", \ + "You try to perform CPR on [C.name]... Hold still!") + if(!do_mob(src, C)) + to_chat(src, "You fail to perform CPR on [C]!") + return 0 + + var/they_breathe = !HAS_TRAIT(C, TRAIT_NOBREATH) + var/they_lung = C.getorganslot(ORGAN_SLOT_LUNGS) + + if(C.health > C.crit_threshold) + return + + src.visible_message("[src] performs CPR on [C.name]!", "You perform CPR on [C.name].") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "perform_cpr", /datum/mood_event/perform_cpr) + C.cpr_time = world.time + log_combat(src, C, "CPRed") + + if(they_breathe && they_lung) + var/suff = min(C.getOxyLoss(), 7) + C.adjustOxyLoss(-suff) + C.updatehealth() + to_chat(C, "You feel a breath of fresh air enter your lungs... It feels good...") + else if(they_breathe && !they_lung) + to_chat(C, "You feel a breath of fresh air... but you don't feel any better...") + else + to_chat(C, "You feel a breath of fresh air... which is a sensation you don't recognise...") + +/mob/living/carbon/human/cuff_resist(obj/item/I) + if(dna && dna.check_mutation(HULK)) + say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") + if(..(I, cuff_break = FAST_CUFFBREAK)) + dropItemToGround(I) + else + if(..()) + dropItemToGround(I) + +/mob/living/carbon/human/clean_blood() + var/mob/living/carbon/human/H = src + if(H.gloves) + if(H.gloves.clean_blood()) + H.update_inv_gloves() + else + ..() // Clear the Blood_DNA list + if(H.bloody_hands) + H.bloody_hands = 0 + H.update_inv_gloves() + update_icons() //apply the now updated overlays to the mob + +/mob/living/carbon/human/wash_cream() + if(creamed) //clean both to prevent a rare bug + cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_lizard")) + cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_human")) + cut_overlay(mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi', "cum_normal")) + cut_overlay(mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi', "cum_large")) + creamed = FALSE + + +//Turns a mob black, flashes a skeleton overlay +//Just like a cartoon! +/mob/living/carbon/human/proc/electrocution_animation(anim_duration) + //Handle mutant parts if possible + if(dna && dna.species) + add_atom_colour("#000000", TEMPORARY_COLOUR_PRIORITY) + var/static/mutable_appearance/electrocution_skeleton_anim + if(!electrocution_skeleton_anim) + electrocution_skeleton_anim = mutable_appearance(icon, "electrocuted_base") + electrocution_skeleton_anim.appearance_flags |= RESET_COLOR|KEEP_APART + add_overlay(electrocution_skeleton_anim) + addtimer(CALLBACK(src, .proc/end_electrocution_animation, electrocution_skeleton_anim), anim_duration) + + else //or just do a generic animation + flick_overlay_view(image(icon,src,"electrocuted_generic",ABOVE_MOB_LAYER), src, anim_duration) + +/mob/living/carbon/human/proc/end_electrocution_animation(mutable_appearance/MA) + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#000000") + cut_overlay(MA) + +/mob/living/carbon/human/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) + if(incapacitated() || lying ) + to_chat(src, "You can't do that right now!") + return FALSE + if(!Adjacent(M) && (M.loc != src)) + if((be_close == 0) || (!no_tk && (dna.check_mutation(TK) && tkMaxRangeCheck(src, M)))) + return TRUE + to_chat(src, "You are too far away!") + return FALSE + return TRUE + +/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/replace_records_name(oldname,newname) // Only humans have records right now, move this up if changed. + for(var/list/L in list(GLOB.data_core.general,GLOB.data_core.medical,GLOB.data_core.security,GLOB.data_core.locked)) + var/datum/data/record/R = find_record("name", oldname, L) + if(R) + R.fields["name"] = newname + +/mob/living/carbon/human/get_total_tint() + . = ..() + if(glasses) + . += glasses.tint + +/mob/living/carbon/human/update_health_hud() + if(!client || !hud_used) + return + if(dna.species.update_health_hud()) + return + else + if(hud_used.healths) + var/health_amount = health - CLAMP(getStaminaLoss()-50, 0, 80)//CIT CHANGE - makes staminaloss have less of an impact on the health hud + if(..(health_amount)) //not dead + switch(hal_screwyhud) + if(SCREWYHUD_CRIT) + hud_used.healths.icon_state = "health6" + if(SCREWYHUD_DEAD) + hud_used.healths.icon_state = "health7" + if(SCREWYHUD_HEALTHY) + hud_used.healths.icon_state = "health0" + if(hud_used.healthdoll) + hud_used.healthdoll.cut_overlays() + if(stat != DEAD) + hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + var/damage = BP.burn_dam + BP.brute_dam + var/comparison = (BP.max_damage/5) + var/icon_num = 0 + if(damage) + icon_num = 1 + if(damage > (comparison)) + icon_num = 2 + if(damage > (comparison*2)) + icon_num = 3 + if(damage > (comparison*3)) + icon_num = 4 + if(damage > (comparison*4)) + icon_num = 5 + if(hal_screwyhud == SCREWYHUD_HEALTHY) + icon_num = 0 + if(icon_num) + hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[BP.body_zone][icon_num]")) + for(var/t in get_missing_limbs()) //Missing limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]6")) + for(var/t in get_disabled_limbs()) //Disabled limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]7")) + else + hud_used.healthdoll.icon_state = "healthdoll_DEAD" + +/mob/living/carbon/human/fully_heal(admin_revive = 0) + if(admin_revive) + regenerate_limbs() + regenerate_organs() + remove_all_embedded_objects() + set_heartattack(FALSE) + drunkenness = 0 + for(var/datum/mutation/human/HM in dna.mutations) + if(HM.quality != POSITIVE) + dna.remove_mutation(HM.name) + if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio)) + blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio) + ..() + +/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker) + . = ..() + if (dna && dna.species) + . += dna.species.check_weakness(weapon, attacker) + +/mob/living/carbon/human/is_literate() + return 1 + +/mob/living/carbon/human/can_hold_items() + return TRUE + +/mob/living/carbon/human/update_gravity(has_gravity,override = 0) + if(dna && dna.species) //prevents a runtime while a human is being monkeyfied + override = dna.species.override_float + ..() + +/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = 0, stun = 1, distance = 0, message = 1, toxic = 0) + if(blood && (NOBLOOD in dna.species.species_traits)) + if(message) + visible_message("[src] dry heaves!", \ + "You try to throw up, but there's nothing in your stomach!") + if(stun) + Knockdown(200) + return 1 + ..() + +/mob/living/carbon/human/vv_get_dropdown() + . = ..() + . += "---" + .["Make monkey"] = "?_src_=vars;[HrefToken()];makemonkey=[REF(src)]" + .["Set Species"] = "?_src_=vars;[HrefToken()];setspecies=[REF(src)]" + .["Make cyborg"] = "?_src_=vars;[HrefToken()];makerobot=[REF(src)]" + .["Make alien"] = "?_src_=vars;[HrefToken()];makealien=[REF(src)]" + .["Make slime"] = "?_src_=vars;[HrefToken()];makeslime=[REF(src)]" + .["Toggle Purrbation"] = "?_src_=vars;[HrefToken()];purrbation=[REF(src)]" + .["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]" + .["Add/Remove Quirks"] = "?_src_=vars;[HrefToken()];modquirks=[REF(src)]" + +/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) + if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) + //If they dragged themselves and we're currently aggressively grabbing them try to piggyback + if(user == target && can_piggyback(target)) + piggyback(target) + return + //If you dragged them to you and you're aggressively grabbing try to fireman carry them + else if(user != target) + fireman_carry(target) + return + . = ..() + +//src is the user that will be carrying, target is the mob to be carried +/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target) + return (istype(target) && target.stat == CONSCIOUS) + +/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target) + return (ishuman(target) && target.lying) + +/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target) + if(can_be_firemanned(target)) + visible_message("[src] starts lifting [target] onto their back...", + "You start lifting [target] onto your back...") + if(do_after(src, 30, TRUE, target)) + //Second check to make sure they're still valid to be carried + if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) + target.resting = FALSE + buckle_mob(target, TRUE, TRUE, 90, 1, 0) + return + visible_message("[src] fails to fireman carry [target]!") + else + to_chat(src, "You can't fireman carry [target] while they're standing!") + +/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target) + if(can_piggyback(target)) + visible_message("[target] starts to climb onto [src]...") + if(do_after(target, 15, target = src)) + if(can_piggyback(target)) + if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) + target.visible_message("[target] can't hang onto [src]!") + return + buckle_mob(target, TRUE, TRUE, FALSE, 0, 2) + else + visible_message("[target] fails to climb onto [src]!") + else + to_chat(target, "You can't piggyback ride [src] right now!") + +/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0) + if(!force)//humans are only meant to be ridden through piggybacking and special cases + return + if(!is_type_in_typecache(target, can_ride_typecache)) + target.visible_message("[target] really can't seem to mount [src]...") + return + buckle_lying = lying_buckle + var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human) + if(target_hands_needed) + riding_datum.ride_check_rider_restrained = TRUE + if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled) + return + var/equipped_hands_self + var/equipped_hands_target + if(hands_needed) + equipped_hands_self = riding_datum.equip_buckle_inhands(src, hands_needed, target) + if(target_hands_needed) + equipped_hands_target = riding_datum.equip_buckle_inhands(target, target_hands_needed) + + if(hands_needed || target_hands_needed) + if(hands_needed && !equipped_hands_self) + src.visible_message("[src] can't get a grip on [target] because their hands are full!", + "You can't get a grip on [target] because your hands are full!") + return + else if(target_hands_needed && !equipped_hands_target) + target.visible_message("[target] can't get a grip on [src] because their hands are full!", + "You can't get a grip on [src] because your hands are full!") + return + + stop_pulling() + riding_datum.handle_vehicle_layer() + . = ..(target, force, check_loc) + +/mob/living/carbon/human/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this + for(var/obj/item/clothing/C in get_equipped_items()) //doesn't include pockets + if(C.blocks_shove_knockdown) + return TRUE + return FALSE + +/mob/living/carbon/human/proc/clear_shove_slowdown() + remove_movespeed_modifier(SHOVE_SLOWDOWN_ID) + var/active_item = get_active_held_item() + if(is_type_in_typecache(active_item, GLOB.shove_disarming_types)) + visible_message("[src.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE) + +/mob/living/carbon/human/do_after_coefficent() + . = ..() + . *= physiology.do_after_speed + +/mob/living/carbon/human/species + var/race = null + +/mob/living/carbon/human/species/Initialize() + . = ..() + set_species(race) + +/mob/living/carbon/human/species/abductor + race = /datum/species/abductor + +/mob/living/carbon/human/species/android + race = /datum/species/android + +/mob/living/carbon/human/species/angel + race = /datum/species/angel + +/mob/living/carbon/human/species/corporate + race = /datum/species/corporate + +/mob/living/carbon/human/species/dullahan + race = /datum/species/dullahan + +/mob/living/carbon/human/species/felinid + race = /datum/species/human/felinid + +/mob/living/carbon/human/species/fly + race = /datum/species/fly + +/mob/living/carbon/human/species/golem + race = /datum/species/golem + +/mob/living/carbon/human/species/golem/random + race = /datum/species/golem/random + +/mob/living/carbon/human/species/golem/adamantine + race = /datum/species/golem/adamantine + +/mob/living/carbon/human/species/golem/plasma + race = /datum/species/golem/plasma + +/mob/living/carbon/human/species/golem/diamond + race = /datum/species/golem/diamond + +/mob/living/carbon/human/species/golem/gold + race = /datum/species/golem/gold + +/mob/living/carbon/human/species/golem/silver + race = /datum/species/golem/silver + +/mob/living/carbon/human/species/golem/plasteel + race = /datum/species/golem/plasteel + +/mob/living/carbon/human/species/golem/titanium + race = /datum/species/golem/titanium + +/mob/living/carbon/human/species/golem/plastitanium + race = /datum/species/golem/plastitanium + +/mob/living/carbon/human/species/golem/alien_alloy + race = /datum/species/golem/alloy + +/mob/living/carbon/human/species/golem/wood + race = /datum/species/golem/wood + +/mob/living/carbon/human/species/golem/uranium + race = /datum/species/golem/uranium + +/mob/living/carbon/human/species/golem/sand + race = /datum/species/golem/sand + +/mob/living/carbon/human/species/golem/glass + race = /datum/species/golem/glass + +/mob/living/carbon/human/species/golem/bluespace + race = /datum/species/golem/bluespace + +/mob/living/carbon/human/species/golem/bananium + race = /datum/species/golem/bananium + +/mob/living/carbon/human/species/golem/blood_cult + race = /datum/species/golem/runic + +/mob/living/carbon/human/species/golem/cloth + race = /datum/species/golem/cloth + +/mob/living/carbon/human/species/golem/plastic + race = /datum/species/golem/plastic + +/mob/living/carbon/human/species/golem/bronze + race = /datum/species/golem/bronze + +/mob/living/carbon/human/species/golem/cardboard + race = /datum/species/golem/cardboard + +/mob/living/carbon/human/species/golem/leather + race = /datum/species/golem/leather + +/mob/living/carbon/human/species/golem/bone + race = /datum/species/golem/bone + +/mob/living/carbon/human/species/golem/durathread + race = /datum/species/golem/durathread + +/mob/living/carbon/human/species/golem/clockwork + race = /datum/species/golem/clockwork + +/mob/living/carbon/human/species/golem/clockwork/no_scrap + race = /datum/species/golem/clockwork/no_scrap + +/mob/living/carbon/human/species/jelly + race = /datum/species/jelly + +/mob/living/carbon/human/species/jelly/slime + race = /datum/species/jelly/slime + +/mob/living/carbon/human/species/jelly/stargazer + race = /datum/species/jelly/stargazer + +/mob/living/carbon/human/species/jelly/luminescent + race = /datum/species/jelly/luminescent + +/mob/living/carbon/human/species/lizard + race = /datum/species/lizard + +/mob/living/carbon/human/species/lizard/ashwalker + race = /datum/species/lizard/ashwalker + +/mob/living/carbon/human/species/moth + race = /datum/species/moth + +/mob/living/carbon/human/species/mush + race = /datum/species/mush + +/mob/living/carbon/human/species/plasma + race = /datum/species/plasmaman + +/mob/living/carbon/human/species/pod + race = /datum/species/pod + +/mob/living/carbon/human/species/shadow + race = /datum/species/shadow + +/mob/living/carbon/human/species/shadow/nightmare + race = /datum/species/shadow/nightmare + +/mob/living/carbon/human/species/skeleton + race = /datum/species/skeleton + +/mob/living/carbon/human/species/synth + race = /datum/species/synth + +/mob/living/carbon/human/species/synth/military + race = /datum/species/synth/military + +/mob/living/carbon/human/species/vampire + race = /datum/species/vampire + +/mob/living/carbon/human/species/zombie + race = /datum/species/zombie + +/mob/living/carbon/human/species/zombie/infectious + race = /datum/species/zombie/infectious + +/mob/living/carbon/human/species/zombie/krokodil_addict + race = /datum/species/krokodil_addict + +/mob/living/carbon/human/species/mammal + race = /datum/species/mammal + +/mob/living/carbon/human/species/insect + race = /datum/species/insect + +/mob/living/carbon/human/species/xeno + race = /datum/species/xeno + +/mob/living/carbon/human/species/ipc + race = /datum/species/ipc + +/mob/living/carbon/human/species/roundstartslime + race = /datum/species/jelly/roundstartslime +//define holder_type on nerds we wanna commit scoop to +/* /mob/living/carbon/human + var/holder_type = /obj/item/clothing/head/mob_holder/micro + can_be_held = "micro" +*/ + diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index d4845acc..4259ba29 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -1,802 +1,808 @@ -/mob/living/carbon/human/getarmor(def_zone, type) - var/armorval = 0 - var/organnum = 0 - - if(def_zone) - if(isbodypart(def_zone)) - var/obj/item/bodypart/bp = def_zone - if(bp.body_part) - return checkarmor(def_zone, type) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(def_zone)) - return checkarmor(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/X in bodyparts) - var/obj/item/bodypart/BP = X - armorval += checkarmor(BP, type) - organnum++ - return (armorval/max(organnum, 1)) - - -/mob/living/carbon/human/proc/checkarmor(obj/item/bodypart/def_zone, d_type) - if(!d_type) - return 0 - var/protection = 0 - var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, 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(istype(bp, /obj/item/clothing)) - var/obj/item/clothing/C = bp - if(C.body_parts_covered & def_zone.body_part) - protection += C.armor.getRating(d_type) - protection += physiology.armor.getRating(d_type) - return protection - -/mob/living/carbon/human/on_hit(obj/item/projectile/P) - if(dna && dna.species) - dna.species.on_hit(P, src) - - -/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone) - if(dna && dna.species) - var/spec_return = dna.species.bullet_act(P, src) - if(spec_return) - return spec_return - - if(mind) - if(mind.martial_art && !incapacitated(FALSE, TRUE) && mind.martial_art.can_use(src) && mind.martial_art.deflection_chance) //Some martial arts users can deflect projectiles! - if(prob(mind.martial_art.deflection_chance)) - if(!lying && dna && !dna.check_mutation(HULK)) //But only if they're not lying down, and hulks can't do it - if(mind.martial_art.deflection_chance >= 100) //if they can NEVER be hit, lets clue sec in ;) - visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!", "You deflect the projectile!") - else - visible_message("[src] deflects the projectile!", "You deflect the projectile!") - playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, 1) - if(!mind.martial_art.reroute_deflection) - return FALSE - else - P.firer = src - P.setAngle(rand(0, 360))//SHING - return FALSE - return ..() - -/mob/living/carbon/human/check_reflect(def_zone) - if(wear_suit?.IsReflect(def_zone)) - return TRUE - return ..() - -/mob/living/carbon/human/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) - . = ..() - if(.) - return - var/block_chance_modifier = round(damage / -3) - 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 TRUE - 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 TRUE - if(wear_neck) - var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier - if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - return FALSE - -/mob/living/carbon/human/can_embed(obj/item/I) - if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types)) - return TRUE - return FALSE - -/mob/living/carbon/human/proc/check_block() - if(mind) - if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) - return TRUE - return FALSE - -/mob/living/carbon/human/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - return dna?.species?.spec_hitby(AM, src) || ..() - -/mob/living/carbon/human/grabbedby(mob/living/carbon/user, supress_message = 0) - if(user == src && pulling && !pulling.anchored && grab_state >= GRAB_AGGRESSIVE && (HAS_TRAIT(src, TRAIT_FAT)) && ismonkey(pulling)) - devour_mob(pulling) - else - ..() - -/mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE) - if(w_uniform) - w_uniform.add_fingerprint(user) - ..() - - -/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user) - if(!I || !user) - return 0 - - var/obj/item/bodypart/affecting - if(user == src) - affecting = get_bodypart(check_zone(user.zone_selected)) //stabbing yourself always hits the right target - else - affecting = get_bodypart(ran_zone(user.zone_selected)) - var/target_area = parse_zone(check_zone(user.zone_selected)) //our intended target - - SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) - - SSblackbox.record_feedback("nested tally", "item_used_for_combat", 1, list("[I.force]", "[I.type]")) - SSblackbox.record_feedback("tally", "zone_targeted", 1, target_area) - - // the attacked_by code varies among species - return dna.species.spec_attacked_by(I, user, affecting, a_intent, src) - - -/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - if(user.a_intent == INTENT_HARM) - . = ..(user, TRUE) - if(.) - return - var/hulk_verb = pick("smash","pummel") - playsound(loc, user.dna.species.attack_sound, 25, 1, -1) - var/message = "[user] has [hulk_verb]ed [src]!" - visible_message("[message]", \ - "[message]") - adjustBruteLoss(15) - return 1 - -/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_paw(mob/living/carbon/monkey/M) - var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - if(M.a_intent == INTENT_HELP) - return ..() //shaking - - if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stunned instead. - var/obj/item/I = get_active_held_item() - if(I && dropItemToGround(I)) - playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) - visible_message("[M] disarmed [src]!", \ - "[M] disarmed [src]!") - else if(!M.client || prob(5)) // only natural monkeys get to stun reliably, (they only do it occasionaly) - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - Knockdown(100) - log_combat(M, src, "tackled") - visible_message("[M] has tackled down [src]!", \ - "[M] has tackled down [src]!") - - if(M.limb_destroyer) - dismembering_strike(M, affecting.body_zone) - - if(can_inject(M, 1, affecting))//Thick suits can stop monkey bites. - if(..()) //successful monkey bite, this handles disease contraction. - var/damage = rand(1, 3) - apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, "melee")) - return 1 - -/mob/living/carbon/human/attack_alien(mob/living/carbon/alien/humanoid/M) - . = ..() - if(!.) - return - if(M.a_intent == INTENT_HARM) - if (w_uniform) - w_uniform.add_fingerprint(M) - var/damage = prob(90) ? 20 : 0 - if(!damage) - playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) - visible_message("[M] has lunged at [src]!", \ - "[M] has lunged at [src]!") - return 0 - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee", null, null,10) - playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) - visible_message("[M] has slashed at [src]!", \ - "[M] has slashed at [src]!") - log_combat(M, src, "attacked") - if(!dismembering_strike(M, M.zone_selected)) //Dismemberment successful - return 1 - apply_damage(damage, BRUTE, affecting, armor_block) - if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stun instead. - var/obj/item/I = get_active_held_item() - if(I && dropItemToGround(I)) - playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) - visible_message("[M] disarmed [src]!", \ - "[M] disarmed [src]!") - else - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - if(!lying) //CITADEL EDIT - Knockdown(100, TRUE, FALSE, 30, 25) - else - Knockdown(100) - log_combat(M, src, "tackled") - visible_message("[M] has tackled down [src]!", \ - "[M] has tackled down [src]!") - -/mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L) - . = ..() - if(!.) //unsuccessful larva bite. - return - var/damage = rand(1, 3) - if(stat != DEAD) - L.amount_grown = min(L.amount_grown + damage, L.max_grown) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) - - -/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M) - . = ..() - if(.) - var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) - var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(!dam_zone) //Dismemberment successful - return TRUE - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor = run_armor_check(affecting, "melee", armour_penetration = M.armour_penetration) - apply_damage(damage, M.melee_damage_type, affecting, armor) - - -/mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) - . = ..() - if(!.) //unsuccessful slime attack - return - var/damage = rand(5, 25) - if(M.is_adult) - damage = rand(10, 35) - - var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(!dam_zone) //Dismemberment successful - return 1 - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_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) - M.do_attack_animation(src) - if(M.damtype == "brute") - step_away(src,M,15) - var/obj/item/bodypart/temp = get_bodypart(pick(BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_HEAD)) - if(temp) - var/update = 0 - var/dmg = rand(M.force/2, M.force) - var/atom/throw_target = get_edge_target_turf(src, M.dir) - switch(M.damtype) - if("brute") - if(M.force > 35) // durand and other heavy mechas - Knockdown(50) - src.throw_at(throw_target, rand(1,5), 7) - else if(M.force >= 20 && !IsKnockdown()) // lightweight mechas like gygax - Knockdown(30) - src.throw_at(throw_target, rand(1,3), 7) - update |= temp.receive_damage(dmg, 0) - playsound(src, 'sound/weapons/punch4.ogg', 50, 1) - if("fire") - update |= temp.receive_damage(0, dmg) - playsound(src, 'sound/items/welder.ogg', 50, 1) - if("tox") - M.mech_toxin_damage(src) - else - return - if(update) - update_damage_overlays() - updatehealth() - - visible_message("[M.name] has hit [src]!", \ - "[M.name] has hit [src]!", null, COMBAT_MESSAGE_RANGE) - log_combat(M.occupant, src, "attacked", M, "(INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") - - else - ..() - - -/mob/living/carbon/human/ex_act(severity, target, origin) - if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) - return - ..() - if (!severity) - return - var/b_loss = 0 - var/f_loss = 0 - var/bomb_armor = getarmor(null, "bomb") - - switch (severity) - if (1) - if(prob(bomb_armor)) - b_loss = 500 - var/atom/throw_target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src))) - throw_at(throw_target, 200, 4) - damage_clothes(400 - bomb_armor, BRUTE, "bomb") - else - for(var/I in contents) - var/atom/A = I - A.ex_act(severity) - gib() - return - - if (2) - b_loss = 60 - f_loss = 60 - if(bomb_armor) - b_loss = 30*(2 - round(bomb_armor*0.01, 0.05)) - f_loss = b_loss - damage_clothes(200 - bomb_armor, BRUTE, "bomb") - if (!istype(ears, /obj/item/clothing/ears/earmuffs)) - adjustEarDamage(30, 120) - if (prob(max(70 - (bomb_armor * 0.5), 0))) - Unconscious(200) - - if(3) - b_loss = 30 - if(bomb_armor) - b_loss = 15*(2 - round(bomb_armor*0.01, 0.05)) - damage_clothes(max(50 - bomb_armor, 0), BRUTE, "bomb") - if (!istype(ears, /obj/item/clothing/ears/earmuffs)) - adjustEarDamage(15,60) - if (prob(max(50 - (bomb_armor * 0.5), 0))) - Unconscious(160) - - take_overall_damage(b_loss,f_loss) - - //attempt to dismember bodyparts - if(severity <= 2 || !bomb_armor) - var/max_limb_loss = round(4/severity) //so you don't lose four limbs at severity 3. - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - if(prob(50/severity) && !prob(getarmor(BP, "bomb")) && BP.body_zone != BODY_ZONE_HEAD && BP.body_zone != BODY_ZONE_CHEST) - BP.brute_dam = BP.max_damage - BP.dismember() - max_limb_loss-- - if(!max_limb_loss) - break - - -/mob/living/carbon/human/blob_act(obj/structure/blob/B) - if(stat == DEAD) - return - show_message("The blob attacks you!") - var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee")) - - -//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 = 0, override = 0, tesla_shock = 0, illusion = 0, 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(flags_1 & TESLA_IGNORE_1) - 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)) - var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) - heart.beating = TRUE - if(stat == CONSCIOUS) - to_chat(src, "You feel your heart beating again!") - siemens_coeff *= physiology.siemens_coeff - . = ..(shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) - if(.) - electrocution_animation(40) - - -/mob/living/carbon/human/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_CONTENTS) - return - var/informed = FALSE - for(var/obj/item/bodypart/L in src.bodyparts) - if(L.status == BODYPART_ROBOTIC) - if(!informed) - to_chat(src, "You feel a sharp pain as your robotic limbs overload.") - informed = TRUE - switch(severity) - if(1) - L.receive_damage(0,10) - Stun(200) - if(2) - L.receive_damage(0,5) - Stun(100) - -/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) - 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 == BODY_ZONE_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(wear_neck) - head_clothes = wear_neck - 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_neck() - update_inv_head() - else - to_chat(src, "Your [head_clothes.name] protects your head and face from the acid!") - else - . = get_bodypart(BODY_ZONE_HEAD) - if(.) - damaged += . - if(ears) - inventory_items_to_kill += ears - - //CHEST// - if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_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_bodypart(BODY_ZONE_CHEST) - if(.) - damaged += . - if(wear_id) - inventory_items_to_kill += wear_id - 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 == BODY_ZONE_L_ARM || bodyzone_hit == BODY_ZONE_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_bodypart(BODY_ZONE_R_ARM) - if(.) - damaged += . - . = get_bodypart(BODY_ZONE_L_ARM) - if(.) - damaged += . - - - //LEGS & FEET// - if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_L_LEG || bodyzone_hit == BODY_ZONE_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_bodypart(BODY_ZONE_R_LEG) - if(.) - damaged += . - . = get_bodypart(BODY_ZONE_L_LEG) - if(.) - damaged += . - - - //DAMAGE// - for(var/obj/item/bodypart/affecting in damaged) - affecting.receive_damage(acidity, 2*acidity) - - if(affecting.name == BODY_ZONE_HEAD) - if(prob(min(acidpwr*acid_volume/10, 90))) //Applies disfigurement - affecting.receive_damage(acidity, 2*acidity) - emote("scream") - facial_hair_style = "Shaved" - hair_style = "Bald" - update_hair() - ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) - - update_damage_overlays() - - //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 - - inventory_items_to_kill += held_items - - for(var/obj/item/I in inventory_items_to_kill) - I.acid_act(acidpwr, acid_volume) - return 1 - -/mob/living/carbon/human/singularity_act() - var/gain = 20 - if(mind) - if((mind.assigned_role == "Station Engineer") || (mind.assigned_role == "Chief Engineer") ) - gain = 100 - if(mind.assigned_role == "Clown") - gain = rand(-300, 300) - investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up! - gib() - return(gain) - -/mob/living/carbon/human/help_shake_act(mob/living/carbon/M) - if(!istype(M)) - return - - if(health >= 0) - if(src == M) - var/to_send = "" - visible_message("[src] examines [p_them()]self.", \ - "You check yourself for injuries.") - - var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - for(var/X in bodyparts) - var/obj/item/bodypart/LB = X - missing -= LB.body_zone - if(LB.is_pseudopart) //don't show injury text for fake bodyparts; ie chainsaw arms or synthetic armblades - continue - var/limb_max_damage = LB.max_damage - var/status = "" - var/brutedamage = LB.brute_dam - var/burndamage = LB.burn_dam - var/broken = LB.broken - if(hallucination) - if(prob(30)) - brutedamage += rand(30,40) - if(prob(30)) - burndamage += rand(30,40) - - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - status = "[brutedamage] brute damage and [burndamage] burn damage" - if(!brutedamage && !burndamage) - status = "no damage" - - else - if(brutedamage > 0) - status = LB.light_brute_msg - if(brutedamage > (limb_max_damage*0.4)) - status = LB.medium_brute_msg - if(brutedamage > (limb_max_damage*0.8)) - status = LB.heavy_brute_msg - if(brutedamage > 0 && burndamage > 0) - status += " and " - - if(burndamage > (limb_max_damage*0.8)) - status += LB.heavy_burn_msg - else if(burndamage > (limb_max_damage*0.2)) - status += LB.medium_burn_msg - else if(burndamage > 0) - status += LB.light_burn_msg - if(broken == 1) - status = "fractured" - if(status == "") - status = "OK" - var/no_damage - if(status == "OK" || status == "no damage") - no_damage = TRUE - to_send += "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].\n" - - for(var/obj/item/I in LB.embedded_objects) - to_send += "\t There is \a [I] embedded in your [LB.name]!\n" - - for(var/t in missing) - for(var/t in missing) - to_send += "Your [parse_zone(t)] is missing!\n" - - if(bleed_rate) - to_send += "You are bleeding!\n" - if(getStaminaLoss()) - if(getStaminaLoss() > 30) - to_send += "You're completely exhausted.\n" - else - to_send += "You feel fatigued.\n" - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - if(toxloss) - if(toxloss > 10) - to_send += "You feel sick.\n" - else if(toxloss > 20) - to_send += "You feel nauseated.\n" - else if(toxloss > 40) - to_send += "You feel very unwell!\n" - if(oxyloss) - if(oxyloss > 10) - to_send += "You feel lightheaded.\n" - else if(oxyloss > 20) - to_send += "Your thinking is clouded and distant.\n" - else if(oxyloss > 30) - to_send += "You're choking!\n" - - switch(nutrition) - if(NUTRITION_LEVEL_FULL to INFINITY) - to_send += "You're completely stuffed!\n" - if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) - to_send += "You're well fed!\n" - if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) - to_send += "You're not hungry.\n" - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) - to_send += "You could use a bite to eat.\n" - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) - to_send += "You feel quite hungry.\n" - if(0 to NUTRITION_LEVEL_STARVING) - to_send += "You're starving!\n" - - - //TODO: Convert these messages into vague messages, thereby encouraging actual dignosis. - //Compiles then shows the list of damaged organs and broken organs - var/list/broken = list() - var/list/damaged = list() - var/broken_message - var/damaged_message - var/broken_plural - var/damaged_plural - //Sets organs into their proper list - for(var/O in internal_organs) - var/obj/item/organ/organ = O - if(organ.organ_flags & ORGAN_FAILING) - if(broken.len) - broken += ", " - broken += organ.name - else if(organ.damage > organ.low_threshold) - if(damaged.len) - damaged += ", " - damaged += organ.name - //Checks to enforce proper grammar, inserts words as necessary into the list - if(broken.len) - if(broken.len > 1) - broken.Insert(broken.len, "and ") - broken_plural = TRUE - else - var/holder = broken[1] //our one and only element - if(holder[length(holder)] == "s") - broken_plural = TRUE - //Put the items in that list into a string of text - for(var/B in broken) - broken_message += B - to_chat(src, " Your [broken_message] [broken_plural ? "are" : "is"] non-functional!") - if(damaged.len) - if(damaged.len > 1) - damaged.Insert(damaged.len, "and ") - damaged_plural = TRUE - else - var/holder = damaged[1] - if(holder[length(holder)] == "s") - damaged_plural = TRUE - for(var/D in damaged) - damaged_message += D - to_chat(src, "Your [damaged_message] [damaged_plural ? "are" : "is"] hurt.") - - if(roundstart_quirks.len) - to_send += "You have these quirks: [get_trait_string()].\n" - - to_chat(src, to_send) - else - if(wear_suit) - wear_suit.add_fingerprint(M) - else if(w_uniform) - w_uniform.add_fingerprint(M) - - ..() - - -/mob/living/carbon/human/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) - if(damage_type != BRUTE && damage_type != BURN) - return - damage_amount *= 0.5 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed - var/list/torn_items = list() - - //HEAD// - if(!def_zone || def_zone == BODY_ZONE_HEAD) - var/obj/item/clothing/head_clothes = null - if(glasses) - head_clothes = glasses - if(wear_mask) - head_clothes = wear_mask - if(wear_neck) - head_clothes = wear_neck - if(head) - head_clothes = head - if(head_clothes) - torn_items += head_clothes - else if(ears) - torn_items += ears - - //CHEST// - if(!def_zone || def_zone == BODY_ZONE_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) - torn_items += chest_clothes - - //ARMS & HANDS// - if(!def_zone || def_zone == BODY_ZONE_L_ARM || def_zone == BODY_ZONE_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) - torn_items |= arm_clothes - - //LEGS & FEET// - if(!def_zone || def_zone == BODY_ZONE_L_LEG || def_zone == BODY_ZONE_R_LEG) - var/obj/item/clothing/leg_clothes = null - if(shoes) - leg_clothes = shoes - if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (w_uniform.body_parts_covered & LEGS))) - leg_clothes = w_uniform - if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (wear_suit.body_parts_covered & LEGS))) - leg_clothes = wear_suit - if(leg_clothes) - torn_items |= leg_clothes - - for(var/obj/item/I in torn_items) - I.take_damage(damage_amount, damage_type, damage_flag, 0) +/mob/living/carbon/human/getarmor(def_zone, type) + var/armorval = 0 + var/organnum = 0 + + if(def_zone) + if(isbodypart(def_zone)) + var/obj/item/bodypart/bp = def_zone + if(bp.body_part) + return checkarmor(def_zone, type) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(def_zone)) + return checkarmor(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/X in bodyparts) + var/obj/item/bodypart/BP = X + armorval += checkarmor(BP, type) + organnum++ + return (armorval/max(organnum, 1)) + + +/mob/living/carbon/human/proc/checkarmor(obj/item/bodypart/def_zone, d_type) + if(!d_type) + return 0 + var/protection = 0 + var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, 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(istype(bp, /obj/item/clothing)) + var/obj/item/clothing/C = bp + if(C.body_parts_covered & def_zone.body_part) + protection += C.armor.getRating(d_type) + protection += physiology.armor.getRating(d_type) + return protection + +/mob/living/carbon/human/on_hit(obj/item/projectile/P) + if(dna && dna.species) + dna.species.on_hit(P, src) + + +/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone) + if(dna && dna.species) + var/spec_return = dna.species.bullet_act(P, src) + if(spec_return) + return spec_return + + if(mind) + if(mind.martial_art && !incapacitated(FALSE, TRUE) && mind.martial_art.can_use(src) && mind.martial_art.deflection_chance) //Some martial arts users can deflect projectiles! + if(prob(mind.martial_art.deflection_chance)) + if(!lying && dna && !dna.check_mutation(HULK)) //But only if they're not lying down, and hulks can't do it + if(mind.martial_art.deflection_chance >= 100) //if they can NEVER be hit, lets clue sec in ;) + visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!", "You deflect the projectile!") + else + visible_message("[src] deflects the projectile!", "You deflect the projectile!") + playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, 1) + if(!mind.martial_art.reroute_deflection) + return FALSE + else + P.firer = src + P.setAngle(rand(0, 360))//SHING + return FALSE + return ..() + +/mob/living/carbon/human/check_reflect(def_zone) + if(wear_suit?.IsReflect(def_zone)) + return TRUE + return ..() + +/mob/living/carbon/human/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) + . = ..() + if(.) + return + var/block_chance_modifier = round(damage / -3) + 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 TRUE + 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 TRUE + if(wear_neck) + var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier + if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return TRUE + return FALSE + +/mob/living/carbon/human/can_embed(obj/item/I) + if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types)) + return TRUE + return FALSE + +/mob/living/carbon/human/proc/check_block() + if(mind) + if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) + return TRUE + return FALSE + +/mob/living/carbon/human/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + return dna?.species?.spec_hitby(AM, src) || ..() + +/mob/living/carbon/human/grabbedby(mob/living/carbon/user, supress_message = 0) + if(user == src && pulling && !pulling.anchored && grab_state >= GRAB_AGGRESSIVE && (HAS_TRAIT(src, TRAIT_FAT)) && ismonkey(pulling)) + devour_mob(pulling) + else + ..() + +/mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE) + if(w_uniform) + w_uniform.add_fingerprint(user) + ..() + + +/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user) + if(!I || !user) + return 0 + + var/obj/item/bodypart/affecting + if(user == src) + affecting = get_bodypart(check_zone(user.zone_selected)) //stabbing yourself always hits the right target + else + affecting = get_bodypart(ran_zone(user.zone_selected)) + var/target_area = parse_zone(check_zone(user.zone_selected)) //our intended target + + SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) + + SSblackbox.record_feedback("nested tally", "item_used_for_combat", 1, list("[I.force]", "[I.type]")) + SSblackbox.record_feedback("tally", "zone_targeted", 1, target_area) + + // the attacked_by code varies among species + return dna.species.spec_attacked_by(I, user, affecting, a_intent, src) + + +/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM) + . = ..(user, TRUE) + if(.) + return + var/hulk_verb = pick("smash","pummel") + playsound(loc, user.dna.species.attack_sound, 25, 1, -1) + var/message = "[user] has [hulk_verb]ed [src]!" + visible_message("[message]", \ + "[message]") + adjustBruteLoss(15) + return 1 + +/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_paw(mob/living/carbon/monkey/M) + var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + if(M.a_intent == INTENT_HELP) + return ..() //shaking + + if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stunned instead. + var/obj/item/I = get_active_held_item() + if(I && dropItemToGround(I)) + playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) + visible_message("[M] disarmed [src]!", \ + "[M] disarmed [src]!") + else if(!M.client || prob(5)) // only natural monkeys get to stun reliably, (they only do it occasionaly) + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + Knockdown(100) + log_combat(M, src, "tackled") + visible_message("[M] has tackled down [src]!", \ + "[M] has tackled down [src]!") + + if(M.limb_destroyer) + dismembering_strike(M, affecting.body_zone) + + if(can_inject(M, 1, affecting))//Thick suits can stop monkey bites. + if(..()) //successful monkey bite, this handles disease contraction. + var/damage = rand(1, 3) + apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, "melee")) + return 1 + +/mob/living/carbon/human/attack_alien(mob/living/carbon/alien/humanoid/M) + . = ..() + if(!.) + return + if(M.a_intent == INTENT_HARM) + if (w_uniform) + w_uniform.add_fingerprint(M) + var/damage = prob(90) ? 20 : 0 + if(!damage) + playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) + visible_message("[M] has lunged at [src]!", \ + "[M] has lunged at [src]!") + return 0 + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee", null, null,10) + playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) + visible_message("[M] has slashed at [src]!", \ + "[M] has slashed at [src]!") + log_combat(M, src, "attacked") + if(!dismembering_strike(M, M.zone_selected)) //Dismemberment successful + return 1 + apply_damage(damage, BRUTE, affecting, armor_block) + if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stun instead. + var/obj/item/I = get_active_held_item() + if(I && dropItemToGround(I)) + playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) + visible_message("[M] disarmed [src]!", \ + "[M] disarmed [src]!") + else + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + if(!lying) //CITADEL EDIT + Knockdown(100, TRUE, FALSE, 30, 25) + else + Knockdown(100) + log_combat(M, src, "tackled") + visible_message("[M] has tackled down [src]!", \ + "[M] has tackled down [src]!") + +/mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L) + . = ..() + if(!.) //unsuccessful larva bite. + return + var/damage = rand(1, 3) + if(stat != DEAD) + L.amount_grown = min(L.amount_grown + damage, L.max_grown) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block) + + +/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M) + . = ..() + if(.) + var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) + var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(!dam_zone) //Dismemberment successful + return TRUE + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor = run_armor_check(affecting, "melee", armour_penetration = M.armour_penetration) + apply_damage(damage, M.melee_damage_type, affecting, armor) + + +/mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) + . = ..() + if(!.) //unsuccessful slime attack + return + var/damage = rand(5, 25) + if(M.is_adult) + damage = rand(10, 35) + + var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(!dam_zone) //Dismemberment successful + return 1 + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_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) + M.do_attack_animation(src) + if(M.damtype == "brute") + step_away(src,M,15) + var/obj/item/bodypart/temp = get_bodypart(pick(BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_HEAD)) + if(temp) + var/update = 0 + var/dmg = rand(M.force/2, M.force) + var/atom/throw_target = get_edge_target_turf(src, M.dir) + switch(M.damtype) + if("brute") + if(M.force > 35) // durand and other heavy mechas + Knockdown(50) + src.throw_at(throw_target, rand(1,5), 7) + else if(M.force >= 20 && !IsKnockdown()) // lightweight mechas like gygax + Knockdown(30) + src.throw_at(throw_target, rand(1,3), 7) + update |= temp.receive_damage(dmg, 0) + playsound(src, 'sound/weapons/punch4.ogg', 50, 1) + if("fire") + update |= temp.receive_damage(0, dmg) + playsound(src, 'sound/items/welder.ogg', 50, 1) + if("tox") + M.mech_toxin_damage(src) + else + return + if(update) + update_damage_overlays() + updatehealth() + + visible_message("[M.name] has hit [src]!", \ + "[M.name] has hit [src]!", null, COMBAT_MESSAGE_RANGE) + log_combat(M.occupant, src, "attacked", M, "(INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") + + else + ..() + + +/mob/living/carbon/human/ex_act(severity, target, origin) + if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) + return + ..() + if (!severity) + return + var/b_loss = 0 + var/f_loss = 0 + var/bomb_armor = getarmor(null, "bomb") + + switch (severity) + if (1) + if(prob(bomb_armor)) + b_loss = 500 + var/atom/throw_target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src))) + throw_at(throw_target, 200, 4) + damage_clothes(400 - bomb_armor, BRUTE, "bomb") + else + for(var/I in contents) + var/atom/A = I + A.ex_act(severity) + gib() + return + + if (2) + b_loss = 60 + f_loss = 60 + if(bomb_armor) + b_loss = 30*(2 - round(bomb_armor*0.01, 0.05)) + f_loss = b_loss + damage_clothes(200 - bomb_armor, BRUTE, "bomb") + if (!istype(ears, /obj/item/clothing/ears/earmuffs)) + adjustEarDamage(30, 120) + if (prob(max(70 - (bomb_armor * 0.5), 0))) + Unconscious(200) + + if(3) + b_loss = 30 + if(bomb_armor) + b_loss = 15*(2 - round(bomb_armor*0.01, 0.05)) + damage_clothes(max(50 - bomb_armor, 0), BRUTE, "bomb") + if (!istype(ears, /obj/item/clothing/ears/earmuffs)) + adjustEarDamage(15,60) + if (prob(max(50 - (bomb_armor * 0.5), 0))) + Unconscious(160) + + take_overall_damage(b_loss,f_loss) + + //attempt to dismember bodyparts + if(severity <= 2 || !bomb_armor) + var/max_limb_loss = round(4/severity) //so you don't lose four limbs at severity 3. + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + if(prob(50/severity) && !prob(getarmor(BP, "bomb")) && BP.body_zone != BODY_ZONE_HEAD && BP.body_zone != BODY_ZONE_CHEST) + BP.brute_dam = BP.max_damage + BP.dismember() + max_limb_loss-- + if(!max_limb_loss) + break + + +/mob/living/carbon/human/blob_act(obj/structure/blob/B) + if(stat == DEAD) + return + show_message("The blob attacks you!") + var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee")) + + +//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 = 0, override = 0, tesla_shock = 0, illusion = 0, 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(flags_1 & TESLA_IGNORE_1) + 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)) + var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) + heart.beating = TRUE + if(stat == CONSCIOUS) + to_chat(src, "You feel your heart beating again!") + siemens_coeff *= physiology.siemens_coeff + . = ..(shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) + if(.) + electrocution_animation(40) + + +/mob/living/carbon/human/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_CONTENTS) + return + var/informed = FALSE + for(var/obj/item/bodypart/L in src.bodyparts) + if(L.status == BODYPART_ROBOTIC) + if(!informed) + to_chat(src, "You feel a sharp pain as your robotic limbs overload.") + informed = TRUE + switch(severity) + if(1) + L.receive_damage(0,10) + Stun(200) + if(2) + L.receive_damage(0,5) + Stun(100) + +/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) + 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 == BODY_ZONE_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(wear_neck) + head_clothes = wear_neck + 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_neck() + update_inv_head() + else + to_chat(src, "Your [head_clothes.name] protects your head and face from the acid!") + else + . = get_bodypart(BODY_ZONE_HEAD) + if(.) + damaged += . + if(ears) + inventory_items_to_kill += ears + + //CHEST// + if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_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_bodypart(BODY_ZONE_CHEST) + if(.) + damaged += . + if(wear_id) + inventory_items_to_kill += wear_id + 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 == BODY_ZONE_L_ARM || bodyzone_hit == BODY_ZONE_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_bodypart(BODY_ZONE_R_ARM) + if(.) + damaged += . + . = get_bodypart(BODY_ZONE_L_ARM) + if(.) + damaged += . + + + //LEGS & FEET// + if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_L_LEG || bodyzone_hit == BODY_ZONE_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_bodypart(BODY_ZONE_R_LEG) + if(.) + damaged += . + . = get_bodypart(BODY_ZONE_L_LEG) + if(.) + damaged += . + + + //DAMAGE// + for(var/obj/item/bodypart/affecting in damaged) + affecting.receive_damage(acidity, 2*acidity) + + if(affecting.name == BODY_ZONE_HEAD) + if(prob(min(acidpwr*acid_volume/10, 90))) //Applies disfigurement + affecting.receive_damage(acidity, 2*acidity) + emote("scream") + facial_hair_style = "Shaved" + hair_style = "Bald" + update_hair() + ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) + + update_damage_overlays() + + //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 + + inventory_items_to_kill += held_items + + for(var/obj/item/I in inventory_items_to_kill) + I.acid_act(acidpwr, acid_volume) + return 1 + +/mob/living/carbon/human/singularity_act() + var/gain = 20 + if(mind) + if((mind.assigned_role == "Station Engineer") || (mind.assigned_role == "Chief Engineer") ) + gain = 100 + if(mind.assigned_role == "Clown") + gain = rand(-300, 300) + investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up! + gib() + return(gain) + +/mob/living/carbon/human/help_shake_act(mob/living/carbon/M) + if(!istype(M)) + return + + if(health >= 0) + if(src == M) + if(has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) + to_chat(src, "You attempt to remove the durathread strand from around your neck.") + if(do_after(src, 35, null, src)) + to_chat(src, "You succesfuly remove the durathread strand.") + remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + return + var/to_send = "" + visible_message("[src] examines [p_them()]self.", \ + "You check yourself for injuries.") + + var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + for(var/X in bodyparts) + var/obj/item/bodypart/LB = X + missing -= LB.body_zone + if(LB.is_pseudopart) //don't show injury text for fake bodyparts; ie chainsaw arms or synthetic armblades + continue + var/limb_max_damage = LB.max_damage + var/status = "" + var/brutedamage = LB.brute_dam + var/burndamage = LB.burn_dam + var/broken = LB.broken + if(hallucination) + if(prob(30)) + brutedamage += rand(30,40) + if(prob(30)) + burndamage += rand(30,40) + + if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) + status = "[brutedamage] brute damage and [burndamage] burn damage" + if(!brutedamage && !burndamage) + status = "no damage" + + else + if(brutedamage > 0) + status = LB.light_brute_msg + if(brutedamage > (limb_max_damage*0.4)) + status = LB.medium_brute_msg + if(brutedamage > (limb_max_damage*0.8)) + status = LB.heavy_brute_msg + if(brutedamage > 0 && burndamage > 0) + status += " and " + + if(burndamage > (limb_max_damage*0.8)) + status += LB.heavy_burn_msg + else if(burndamage > (limb_max_damage*0.2)) + status += LB.medium_burn_msg + else if(burndamage > 0) + status += LB.light_burn_msg + if(broken == 1) + status = "fractured" + if(status == "") + status = "OK" + var/no_damage + if(status == "OK" || status == "no damage") + no_damage = TRUE + to_send += "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].\n" + + for(var/obj/item/I in LB.embedded_objects) + to_send += "\t There is \a [I] embedded in your [LB.name]!\n" + + for(var/t in missing) + for(var/t in missing) + to_send += "Your [parse_zone(t)] is missing!\n" + + if(bleed_rate) + to_send += "You are bleeding!\n" + if(getStaminaLoss()) + if(getStaminaLoss() > 30) + to_send += "You're completely exhausted.\n" + else + to_send += "You feel fatigued.\n" + if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) + if(toxloss) + if(toxloss > 10) + to_send += "You feel sick.\n" + else if(toxloss > 20) + to_send += "You feel nauseated.\n" + else if(toxloss > 40) + to_send += "You feel very unwell!\n" + if(oxyloss) + if(oxyloss > 10) + to_send += "You feel lightheaded.\n" + else if(oxyloss > 20) + to_send += "Your thinking is clouded and distant.\n" + else if(oxyloss > 30) + to_send += "You're choking!\n" + + switch(nutrition) + if(NUTRITION_LEVEL_FULL to INFINITY) + to_send += "You're completely stuffed!\n" + if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) + to_send += "You're well fed!\n" + if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) + to_send += "You're not hungry.\n" + if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) + to_send += "You could use a bite to eat.\n" + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + to_send += "You feel quite hungry.\n" + if(0 to NUTRITION_LEVEL_STARVING) + to_send += "You're starving!\n" + + + //TODO: Convert these messages into vague messages, thereby encouraging actual dignosis. + //Compiles then shows the list of damaged organs and broken organs + var/list/broken = list() + var/list/damaged = list() + var/broken_message + var/damaged_message + var/broken_plural + var/damaged_plural + //Sets organs into their proper list + for(var/O in internal_organs) + var/obj/item/organ/organ = O + if(organ.organ_flags & ORGAN_FAILING) + if(broken.len) + broken += ", " + broken += organ.name + else if(organ.damage > organ.low_threshold) + if(damaged.len) + damaged += ", " + damaged += organ.name + //Checks to enforce proper grammar, inserts words as necessary into the list + if(broken.len) + if(broken.len > 1) + broken.Insert(broken.len, "and ") + broken_plural = TRUE + else + var/holder = broken[1] //our one and only element + if(holder[length(holder)] == "s") + broken_plural = TRUE + //Put the items in that list into a string of text + for(var/B in broken) + broken_message += B + to_chat(src, " Your [broken_message] [broken_plural ? "are" : "is"] non-functional!") + if(damaged.len) + if(damaged.len > 1) + damaged.Insert(damaged.len, "and ") + damaged_plural = TRUE + else + var/holder = damaged[1] + if(holder[length(holder)] == "s") + damaged_plural = TRUE + for(var/D in damaged) + damaged_message += D + to_chat(src, "Your [damaged_message] [damaged_plural ? "are" : "is"] hurt.") + + if(roundstart_quirks.len) + to_send += "You have these quirks: [get_trait_string()].\n" + + to_chat(src, to_send) + else + if(wear_suit) + wear_suit.add_fingerprint(M) + else if(w_uniform) + w_uniform.add_fingerprint(M) + + ..() + + +/mob/living/carbon/human/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) + if(damage_type != BRUTE && damage_type != BURN) + return + damage_amount *= 0.5 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed + var/list/torn_items = list() + + //HEAD// + if(!def_zone || def_zone == BODY_ZONE_HEAD) + var/obj/item/clothing/head_clothes = null + if(glasses) + head_clothes = glasses + if(wear_mask) + head_clothes = wear_mask + if(wear_neck) + head_clothes = wear_neck + if(head) + head_clothes = head + if(head_clothes) + torn_items += head_clothes + else if(ears) + torn_items += ears + + //CHEST// + if(!def_zone || def_zone == BODY_ZONE_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) + torn_items += chest_clothes + + //ARMS & HANDS// + if(!def_zone || def_zone == BODY_ZONE_L_ARM || def_zone == BODY_ZONE_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) + torn_items |= arm_clothes + + //LEGS & FEET// + if(!def_zone || def_zone == BODY_ZONE_L_LEG || def_zone == BODY_ZONE_R_LEG) + var/obj/item/clothing/leg_clothes = null + if(shoes) + leg_clothes = shoes + if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (w_uniform.body_parts_covered & LEGS))) + leg_clothes = w_uniform + if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (wear_suit.body_parts_covered & LEGS))) + leg_clothes = wear_suit + if(leg_clothes) + torn_items |= leg_clothes + + for(var/obj/item/I in torn_items) + I.take_damage(damage_amount, damage_type, damage_flag, 0) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 34367af9..7126a794 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1519,6 +1519,7 @@ GLOBAL_LIST_EMPTY(roundstart_races) target.lastattacker = user.real_name target.lastattackerckey = user.ckey + user.dna.species.spec_unarmedattacked(user, target) if(user.limb_destroyer) target.dismembering_strike(user, affecting.body_zone) @@ -1532,6 +1533,9 @@ GLOBAL_LIST_EMPTY(roundstart_races) else if(target.lying) target.forcesay(GLOB.hit_appends) +/datum/species/proc/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) + return + /datum/species/proc/disarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) // CITADEL EDIT slap mouthy gits and booty var/aim_for_mouth = user.zone_selected == "mouth" diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index d08eb5ae..c482b6df 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -943,6 +943,21 @@ grab_sound = 'sound/weapons/whipgrab.ogg' attack_sound = 'sound/weapons/whip.ogg' +/datum/species/golem/durathread + name = "Durathread Golem" + id = "durathread golem" + prefix = "Durathread" + limbs_id = "d_golem" + special_names = list("Boll","Weave") + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES) + fixed_mut_color = null + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + info_text = "As a Durathread Golem, your strikes will cause those your targets to start choking, but your woven body won't withstand fire as well." + +/datum/species/golem/durathread/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) + . = ..() + target.apply_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + /datum/species/golem/bone name = "Bone Golem" id = "bone golem" diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 765c50da..13b4a031 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -109,7 +109,7 @@ var/datum/gas_mixture/breath if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if(health <= HEALTH_THRESHOLD_FULLCRIT || (pulledby && pulledby.grab_state >= GRAB_KILL) || lungs.organ_flags & ORGAN_FAILING) + if(health <= HEALTH_THRESHOLD_FULLCRIT || (pulledby && pulledby.grab_state >= GRAB_KILL) || HAS_TRAIT(src, TRAIT_MAGIC_CHOKE) || (lungs && lungs.organ_flags & ORGAN_FAILING)) losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath else if(health <= crit_threshold) diff --git a/code/modules/ruins/lavaland_ruin_code.dm b/code/modules/ruins/lavaland_ruin_code.dm index 8bed71df..13ca684f 100644 --- a/code/modules/ruins/lavaland_ruin_code.dm +++ b/code/modules/ruins/lavaland_ruin_code.dm @@ -85,7 +85,8 @@ /obj/item/stack/tile/bronze = /datum/species/golem/bronze, /obj/item/stack/sheet/cardboard = /datum/species/golem/cardboard, /obj/item/stack/sheet/leather = /datum/species/golem/leather, - /obj/item/stack/sheet/bone = /datum/species/golem/bone) + /obj/item/stack/sheet/bone = /datum/species/golem/bone, + /obj/item/stack/sheet/cotton/durathread = /datum/species/golem/durathread) if(istype(I, /obj/item/stack)) var/obj/item/stack/O = I From 02cb328921c58b606c25a0dea347712d3cbbcfe2 Mon Sep 17 00:00:00 2001 From: BlackMajor Date: Tue, 10 Dec 2019 11:18:55 +1300 Subject: [PATCH 08/21] Merge pull request #9747 from Ghommie/Ghommie-cit329 Fixing AI holopad speech text being small and whispers ending in multiple exclamation marks oxymoron. --- code/datums/brain_damage/imaginary_friend.dm | 4 +- code/game/machinery/doors/passworddoor.dm | 2 +- code/game/machinery/hologram.dm | 1394 +++++------ code/game/mecha/mecha.dm | 2174 ++++++++--------- .../objects/items/devices/radio/intercom.dm | 300 +-- .../game/objects/items/devices/radio/radio.dm | 856 +++---- .../objects/items/devices/taperecorder.dm | 580 ++--- code/game/objects/items/eightball.dm | 2 +- code/game/say.dm | 12 +- .../clockcult/clock_mobs/_eminence.dm | 2 +- .../antagonists/disease/disease_mob.dm | 4 +- code/modules/assembly/voice.dm | 202 +- .../integrated_electronics/subtypes/input.dm | 2 +- code/modules/mob/dead/observer/say.dm | 86 +- code/modules/mob/living/say.dm | 22 +- .../mob/living/silicon/ai/freelook/eye.dm | 394 +-- .../hostile/megafauna/colossus.dm | 2 +- .../mob/living/simple_animal/parrot.dm | 2038 +++++++-------- .../mob/living/simple_animal/slime/say.dm | 2 +- modular_citadel/code/modules/mob/mob.dm | 12 +- 20 files changed, 4046 insertions(+), 4044 deletions(-) diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 47a96e6e..4f500a37 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -149,10 +149,10 @@ friend_talk(message) -/mob/camera/imaginary_friend/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) +/mob/camera/imaginary_friend/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) create_chat_message(speaker, message_language, raw_message, spans, message_mode) - to_chat(src, compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode)) + to_chat(src, compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source)) /mob/camera/imaginary_friend/proc/friend_talk(message) message = capitalize(trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))) diff --git a/code/game/machinery/doors/passworddoor.dm b/code/game/machinery/doors/passworddoor.dm index fcbb214b..2ce2711c 100644 --- a/code/game/machinery/doors/passworddoor.dm +++ b/code/game/machinery/doors/passworddoor.dm @@ -22,7 +22,7 @@ if(voice_activated) flags_1 |= HEAR_1 -/obj/machinery/door/password/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) +/obj/machinery/door/password/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) . = ..() if(!density || !voice_activated || radio_freq) return diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index cf8ec76f..dea57769 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -1,697 +1,697 @@ -/* 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. -Any number of AIs can use a holopad. /Lo6 -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 - */ - -#define HOLOPAD_PASSIVE_POWER_USAGE 1 -#define HOLOGRAM_POWER_USAGE 2 - -/obj/machinery/holopad - name = "holopad" - desc = "It's a floor-mounted device for projecting holographic images." - icon_state = "holopad0" - layer = LOW_OBJ_LAYER - plane = FLOOR_PLANE - flags_1 = HEAR_1 - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 100 - max_integrity = 300 - armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - circuit = /obj/item/circuitboard/machine/holopad - var/list/masters //List of living mobs that use the holopad - var/list/holorays //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/obj/item/disk/holodisk/disk //Record disk - var/replay_mode = FALSE //currently replaying a recording - var/loop_mode = FALSE //currently looping a recording - var/record_mode = FALSE //currently recording - var/record_start = 0 //recording start time - var/record_user //user that inititiated the recording - var/obj/effect/overlay/holo_pad_hologram/replay_holo //replay hologram - var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging - var/static/list/holopads = list() - var/obj/effect/overlay/holoray/ray - var/ringing = FALSE - var/offset = FALSE - var/on_network = TRUE - -/obj/machinery/holopad/tutorial - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - flags_1 = NODECONSTRUCT_1 - on_network = FALSE - var/proximity_range = 1 - -/obj/machinery/holopad/tutorial/Initialize(mapload) - . = ..() - if(proximity_range) - proximity_monitor = new(src, proximity_range) - if(mapload) - var/obj/item/disk/holodisk/new_disk = locate(/obj/item/disk/holodisk) in src.loc - if(new_disk && !disk) - new_disk.forceMove(src) - disk = new_disk - -/obj/machinery/holopad/tutorial/attack_hand(mob/user) - if(!istype(user)) - return - if(user.incapacitated() || !is_operational()) - return - if(replay_mode) - replay_stop() - else if(disk && disk.record) - replay_start() - -/obj/machinery/holopad/tutorial/HasProximity(atom/movable/AM) - if (!isliving(AM)) - return - if(!replay_mode && (disk && disk.record)) - replay_start() - -/obj/machinery/holopad/Initialize() - . = ..() - if(on_network) - holopads += src - -/obj/machinery/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) - - if(replay_mode) - replay_stop() - if(record_mode) - record_stop() - - QDEL_NULL(disk) - - holopads -= src - return ..() - -/obj/machinery/holopad/power_change() - if (powered()) - stat &= ~NOPOWER - else - stat |= NOPOWER - if(replay_mode) - replay_stop() - if(record_mode) - record_stop() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/holopad/obj_break() - . = ..() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/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/holopad/attackby(obj/item/P, mob/user, params) - if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) - return - - if(default_pry_open(P)) - return - - if(default_unfasten_wrench(user, P)) - return - - if(default_deconstruction_crowbar(P)) - return - - if(istype(P,/obj/item/disk/holodisk)) - if(disk) - to_chat(user,"There's already a disk inside [src]") - return - if (!user.transferItemToLoc(P,src)) - return - to_chat(user,"You insert [P] into [src]") - disk = P - updateDialog() - return - - return ..() - - -/obj/machinery/holopad/ui_interact(mob/living/carbon/human/user) //Carn: Hologram requests. - . = ..() - if(!istype(user)) - return - - if(outgoing_call || user.incapacitated() || !is_operational()) - return - - user.set_machine(src) - var/dat - if(temp) - dat = temp - else - if(on_network) - dat += "Request an AI's presence
" - dat += "Call another holopad
" - if(disk) - if(disk.record) - //Replay - dat += "Replay disk recording
" - dat += "Loop disk recording
" - //Clear - dat += "Clear disk recording
" - else - //Record - dat += "Start new recording
" - //Eject - dat += "Eject disk
" - - if(LAZYLEN(holo_calls)) - dat += "=====================================================
" - - if(on_network) - 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/datum/browser/popup = new(user, "holopad", name, 300, 175) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -//Stop ringing the AI!! -/obj/machinery/holopad/proc/hangup_all_calls() - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.Disconnect(src) - -/obj/machinery/holopad/Topic(href, href_list) - if(..() || isAI(usr)) - return - add_fingerprint(usr) - if(!is_operational()) - 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 GLOB.silicon_mobs) - 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 - - 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/result = input(usr, "Choose an area to call", "Holocall") as null|anything in callnames - 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 = locate(href_list["connectcall"]) - if(!QDELETED(call_to_connect)) - call_to_connect.Answer(src) - temp = "" - - else if(href_list["disconnectcall"]) - var/datum/holocall/call_to_disconnect = locate(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() - - else if(href_list["disk_eject"]) - if(disk && !replay_mode) - disk.forceMove(drop_location()) - disk = null - - else if(href_list["replay_stop"]) - replay_stop() - else if(href_list["replay_start"]) - replay_start() - else if(href_list["loop_start"]) - loop_mode = TRUE - replay_start() - else if(href_list["record_start"]) - record_start(usr) - else if(href_list["record_stop"]) - record_stop() - else if(href_list["record_clear"]) - record_clear() - else if(href_list["offset"]) - offset++ - if (offset > 4) - offset = FALSE - var/turf/new_turf - if (!offset) - new_turf = get_turf(src) - else - new_turf = get_step(src, GLOB.cardinals[offset]) - replay_holo.forceMove(new_turf) - updateDialog() - -//do not allow AIs to answer calls or people will use it to meta the AI sattelite -/obj/machinery/holopad/attack_ai(mob/living/silicon/ai/user) - if (!istype(user)) - return - if (!on_network) - 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 != src.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) - else//If there is a hologram, remove it. - clear_holo(user) - -/obj/machinery/holopad/process() - if(LAZYLEN(masters)) - for(var/I in masters) - var/mob/living/master = I - var/mob/living/silicon/ai/AI = master - if(!istype(AI)) - AI = null - - if(!is_operational() || !validate_user(master)) - clear_holo(master) - - if(outgoing_call) - outgoing_call.Check() - - ringing = FALSE - - for(var/I in holo_calls) - var/datum/holocall/HC = I - 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() - -/obj/machinery/holopad/proc/activate_holo(mob/living/user) - var/mob/living/silicon/ai/AI = user - if(!istype(AI)) - AI = null - - if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it - if (AI && istype(AI.current, /obj/machinery/holopad)) - to_chat(user, "ERROR: \black Image feed in progress.") - return - - var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. - if(AI) - Hologram.icon = AI.holo_icon - else //make it like real life - Hologram.icon = user.icon - Hologram.icon_state = user.icon_state - Hologram.copy_overlays(user, TRUE) - //codersprite some holo effects here - Hologram.alpha = 100 - Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) - Hologram.Impersonation = user - - Hologram.copy_known_languages_from(user,replace = TRUE) - 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.setAnchored(TRUE)//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) - visible_message("A holographic image of [user] flickers to life before your eyes!") - - return Hologram - else - to_chat(user, "ERROR: Unable to project hologram.") - -/*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/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker && LAZYLEN(masters) && !radio_freq)//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(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad == src && speaker != HC.hologram) - HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - if(outgoing_call && speaker == outgoing_call.user) - outgoing_call.hologram.say(raw_message) - - if(record_mode && speaker == record_user) - record_message(speaker,raw_message,message_language) - -/obj/machinery/holopad/proc/SetLightsAndPower() - var/total_users = LAZYLEN(masters) + 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 || replay_mode) - set_light(2) - else - set_light(0) - update_icon() - -/obj/machinery/holopad/update_icon() - var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) - if(ringing) - icon_state = "holopad_ringing" - else if(total_users || replay_mode) - icon_state = "holopad1" - else - icon_state = "holopad0" - -/obj/machinery/holopad/proc/set_holo(mob/living/user, var/obj/effect/overlay/holo_pad_hologram/h) - LAZYSET(masters, user, h) - LAZYSET(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/holopad/proc/clear_holo(mob/living/user) - qdel(masters[user]) // Get rid of user's hologram - unset_holo(user) - return TRUE - -/obj/machinery/holopad/proc/unset_holo(mob/living/user) - var/mob/living/silicon/ai/AI = user - if(istype(AI) && AI.current == src) - AI.current = null - LAZYREMOVE(masters, user) // Discard AI from the list of those who use holopad - qdel(holorays[user]) - LAZYREMOVE(holorays, user) - SetLightsAndPower() - return TRUE - -//Try to transfer hologram to another pad that can project on T -/obj/machinery/holopad/proc/transfer_to_nearby_pad(turf/T,mob/holo_owner) - var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] - if(!h || h.HC) //Holocalls can't change source. - return FALSE - for(var/pad in holopads) - var/obj/machinery/holopad/another = pad - if(another == src) - continue - if(another.validate_location(T)) - unset_holo(holo_owner) - if(another.masters && another.masters[holo_owner]) - another.clear_holo(holo_owner) - another.set_holo(holo_owner, h) - return TRUE - return FALSE - -/obj/machinery/holopad/proc/validate_user(mob/living/user) - if(QDELETED(user) || user.incapacitated() || !user.client) - 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/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 - else - return FALSE - -/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) - if(LAZYLEN(masters) && 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.forceMove(new_turf) - if(!transfered) - update_holoray(user,new_turf) - return TRUE - - -/obj/machinery/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) - -// RECORDED MESSAGES - -/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) - var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. - Hologram.add_overlay(record.caller_image) - Hologram.alpha = 170 - Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) - Hologram.dir = SOUTH //for now - Hologram.grant_all_languages(omnitongue=TRUE) - var/datum/language_holder/holder = Hologram.get_language_holder() - holder.selected_default_language = record.language - 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.setAnchored(TRUE)//So space wind cannot drag it. - Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click. - Hologram.set_light(2) //hologram lighting - visible_message("A holographic image of [record.caller_name] flickers to life before your eyes!") - return Hologram - -/obj/machinery/holopad/proc/replay_start() - if(!replay_mode) - replay_mode = TRUE - replay_holo = setup_replay_holo(disk.record) - temp = "Replaying...
" - temp += "Change offset
" - temp += "End replay" - SetLightsAndPower() - replay_entry(1) - return - -/obj/machinery/holopad/proc/replay_stop() - if(replay_mode) - replay_mode = FALSE - loop_mode = FALSE - offset = FALSE - temp = null - QDEL_NULL(replay_holo) - SetLightsAndPower() - updateDialog() - -/obj/machinery/holopad/proc/record_start(mob/living/user) - if(!user || !disk || disk.record) - return - disk.record = new - record_mode = TRUE - record_start = world.time - record_user = user - disk.record.set_caller_image(user) - temp = "Recording...
" - temp += "End recording." - -/obj/machinery/holopad/proc/record_message(mob/living/speaker,message,language) - if(!record_mode) - return - //make this command so you can have multiple languages in single record - if((!disk.record.caller_name || disk.record.caller_name == "Unknown") && istype(speaker)) - disk.record.caller_name = speaker.name - if(!disk.record.language) - disk.record.language = language - else if(language != disk.record.language) - disk.record.entries += list(list(HOLORECORD_LANGUAGE,language)) - - var/current_delay = 0 - for(var/E in disk.record.entries) - var/list/entry = E - if(entry[1] != HOLORECORD_DELAY) - continue - current_delay += entry[2] - - var/time_delta = world.time - record_start - current_delay - - if(time_delta >= 1) - disk.record.entries += list(list(HOLORECORD_DELAY,time_delta)) - disk.record.entries += list(list(HOLORECORD_SAY,message)) - if(disk.record.entries.len >= HOLORECORD_MAX_LENGTH) - record_stop() - -/obj/machinery/holopad/proc/replay_entry(entry_number) - if(!replay_mode) - return - if (!disk.record.entries.len) // check for zero entries such as photographs and no text recordings - return // and pretty much just display them statically untill manually stopped - if(disk.record.entries.len < entry_number) - if(loop_mode) - entry_number = 1 - else - replay_stop() - return - var/list/entry = disk.record.entries[entry_number] - var/command = entry[1] - switch(command) - if(HOLORECORD_SAY) - var/message = entry[2] - if(replay_holo) - replay_holo.say(message) - if(HOLORECORD_SOUND) - playsound(src,entry[2],50,1) - if(HOLORECORD_DELAY) - addtimer(CALLBACK(src,.proc/replay_entry,entry_number+1),entry[2]) - return - if(HOLORECORD_LANGUAGE) - var/datum/language_holder/holder = replay_holo.get_language_holder() - holder.selected_default_language = entry[2] - if(HOLORECORD_PRESET) - var/preset_type = entry[2] - var/datum/preset_holoimage/H = new preset_type - replay_holo.cut_overlays() - replay_holo.add_overlay(H.build_image()) - if(HOLORECORD_RENAME) - replay_holo.name = entry[2] + " (Hologram)" - .(entry_number+1) - -/obj/machinery/holopad/proc/record_stop() - if(record_mode) - record_mode = FALSE - temp = null - record_user = null - updateDialog() - -/obj/machinery/holopad/proc/record_clear() - if(disk && disk.record) - QDEL_NULL(disk.record) - updateDialog() - -/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 TRUE - -/obj/effect/overlay/holo_pad_hologram/examine(mob/user) - if(Impersonation) - return Impersonation.examine(user) - return ..() - -/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_TRANSPARENT - pixel_x = -32 - pixel_y = -32 - alpha = 100 - -#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. +Any number of AIs can use a holopad. /Lo6 +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 + */ + +#define HOLOPAD_PASSIVE_POWER_USAGE 1 +#define HOLOGRAM_POWER_USAGE 2 + +/obj/machinery/holopad + name = "holopad" + desc = "It's a floor-mounted device for projecting holographic images." + icon_state = "holopad0" + layer = LOW_OBJ_LAYER + plane = FLOOR_PLANE + flags_1 = HEAR_1 + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 100 + max_integrity = 300 + armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) + circuit = /obj/item/circuitboard/machine/holopad + var/list/masters //List of living mobs that use the holopad + var/list/holorays //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/obj/item/disk/holodisk/disk //Record disk + var/replay_mode = FALSE //currently replaying a recording + var/loop_mode = FALSE //currently looping a recording + var/record_mode = FALSE //currently recording + var/record_start = 0 //recording start time + var/record_user //user that inititiated the recording + var/obj/effect/overlay/holo_pad_hologram/replay_holo //replay hologram + var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging + var/static/list/holopads = list() + var/obj/effect/overlay/holoray/ray + var/ringing = FALSE + var/offset = FALSE + var/on_network = TRUE + +/obj/machinery/holopad/tutorial + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + flags_1 = NODECONSTRUCT_1 + on_network = FALSE + var/proximity_range = 1 + +/obj/machinery/holopad/tutorial/Initialize(mapload) + . = ..() + if(proximity_range) + proximity_monitor = new(src, proximity_range) + if(mapload) + var/obj/item/disk/holodisk/new_disk = locate(/obj/item/disk/holodisk) in src.loc + if(new_disk && !disk) + new_disk.forceMove(src) + disk = new_disk + +/obj/machinery/holopad/tutorial/attack_hand(mob/user) + if(!istype(user)) + return + if(user.incapacitated() || !is_operational()) + return + if(replay_mode) + replay_stop() + else if(disk && disk.record) + replay_start() + +/obj/machinery/holopad/tutorial/HasProximity(atom/movable/AM) + if (!isliving(AM)) + return + if(!replay_mode && (disk && disk.record)) + replay_start() + +/obj/machinery/holopad/Initialize() + . = ..() + if(on_network) + holopads += src + +/obj/machinery/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) + + if(replay_mode) + replay_stop() + if(record_mode) + record_stop() + + QDEL_NULL(disk) + + holopads -= src + return ..() + +/obj/machinery/holopad/power_change() + if (powered()) + stat &= ~NOPOWER + else + stat |= NOPOWER + if(replay_mode) + replay_stop() + if(record_mode) + record_stop() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/holopad/obj_break() + . = ..() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/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/holopad/attackby(obj/item/P, mob/user, params) + if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) + return + + if(default_pry_open(P)) + return + + if(default_unfasten_wrench(user, P)) + return + + if(default_deconstruction_crowbar(P)) + return + + if(istype(P,/obj/item/disk/holodisk)) + if(disk) + to_chat(user,"There's already a disk inside [src]") + return + if (!user.transferItemToLoc(P,src)) + return + to_chat(user,"You insert [P] into [src]") + disk = P + updateDialog() + return + + return ..() + + +/obj/machinery/holopad/ui_interact(mob/living/carbon/human/user) //Carn: Hologram requests. + . = ..() + if(!istype(user)) + return + + if(outgoing_call || user.incapacitated() || !is_operational()) + return + + user.set_machine(src) + var/dat + if(temp) + dat = temp + else + if(on_network) + dat += "Request an AI's presence
" + dat += "Call another holopad
" + if(disk) + if(disk.record) + //Replay + dat += "Replay disk recording
" + dat += "Loop disk recording
" + //Clear + dat += "Clear disk recording
" + else + //Record + dat += "Start new recording
" + //Eject + dat += "Eject disk
" + + if(LAZYLEN(holo_calls)) + dat += "=====================================================
" + + if(on_network) + 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/datum/browser/popup = new(user, "holopad", name, 300, 175) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +//Stop ringing the AI!! +/obj/machinery/holopad/proc/hangup_all_calls() + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.Disconnect(src) + +/obj/machinery/holopad/Topic(href, href_list) + if(..() || isAI(usr)) + return + add_fingerprint(usr) + if(!is_operational()) + 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 GLOB.silicon_mobs) + 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 + + 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/result = input(usr, "Choose an area to call", "Holocall") as null|anything in callnames + 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 = locate(href_list["connectcall"]) + if(!QDELETED(call_to_connect)) + call_to_connect.Answer(src) + temp = "" + + else if(href_list["disconnectcall"]) + var/datum/holocall/call_to_disconnect = locate(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() + + else if(href_list["disk_eject"]) + if(disk && !replay_mode) + disk.forceMove(drop_location()) + disk = null + + else if(href_list["replay_stop"]) + replay_stop() + else if(href_list["replay_start"]) + replay_start() + else if(href_list["loop_start"]) + loop_mode = TRUE + replay_start() + else if(href_list["record_start"]) + record_start(usr) + else if(href_list["record_stop"]) + record_stop() + else if(href_list["record_clear"]) + record_clear() + else if(href_list["offset"]) + offset++ + if (offset > 4) + offset = FALSE + var/turf/new_turf + if (!offset) + new_turf = get_turf(src) + else + new_turf = get_step(src, GLOB.cardinals[offset]) + replay_holo.forceMove(new_turf) + updateDialog() + +//do not allow AIs to answer calls or people will use it to meta the AI sattelite +/obj/machinery/holopad/attack_ai(mob/living/silicon/ai/user) + if (!istype(user)) + return + if (!on_network) + 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 != src.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) + else//If there is a hologram, remove it. + clear_holo(user) + +/obj/machinery/holopad/process() + if(LAZYLEN(masters)) + for(var/I in masters) + var/mob/living/master = I + var/mob/living/silicon/ai/AI = master + if(!istype(AI)) + AI = null + + if(!is_operational() || !validate_user(master)) + clear_holo(master) + + if(outgoing_call) + outgoing_call.Check() + + ringing = FALSE + + for(var/I in holo_calls) + var/datum/holocall/HC = I + 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() + +/obj/machinery/holopad/proc/activate_holo(mob/living/user) + var/mob/living/silicon/ai/AI = user + if(!istype(AI)) + AI = null + + if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it + if (AI && istype(AI.current, /obj/machinery/holopad)) + to_chat(user, "ERROR: \black Image feed in progress.") + return + + var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. + if(AI) + Hologram.icon = AI.holo_icon + else //make it like real life + Hologram.icon = user.icon + Hologram.icon_state = user.icon_state + Hologram.copy_overlays(user, TRUE) + //codersprite some holo effects here + Hologram.alpha = 100 + Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) + Hologram.Impersonation = user + + Hologram.copy_known_languages_from(user,replace = TRUE) + 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.setAnchored(TRUE)//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) + visible_message("A holographic image of [user] flickers to life before your eyes!") + + return Hologram + else + to_chat(user, "ERROR: Unable to project hologram.") + +/*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/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(speaker && LAZYLEN(masters) && !radio_freq)//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(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad == src && speaker != HC.hologram) + HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode, source) + + if(outgoing_call && speaker == outgoing_call.user) + outgoing_call.hologram.say(raw_message) + + if(record_mode && speaker == record_user) + record_message(speaker,raw_message,message_language) + +/obj/machinery/holopad/proc/SetLightsAndPower() + var/total_users = LAZYLEN(masters) + 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 || replay_mode) + set_light(2) + else + set_light(0) + update_icon() + +/obj/machinery/holopad/update_icon() + var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) + if(ringing) + icon_state = "holopad_ringing" + else if(total_users || replay_mode) + icon_state = "holopad1" + else + icon_state = "holopad0" + +/obj/machinery/holopad/proc/set_holo(mob/living/user, var/obj/effect/overlay/holo_pad_hologram/h) + LAZYSET(masters, user, h) + LAZYSET(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/holopad/proc/clear_holo(mob/living/user) + qdel(masters[user]) // Get rid of user's hologram + unset_holo(user) + return TRUE + +/obj/machinery/holopad/proc/unset_holo(mob/living/user) + var/mob/living/silicon/ai/AI = user + if(istype(AI) && AI.current == src) + AI.current = null + LAZYREMOVE(masters, user) // Discard AI from the list of those who use holopad + qdel(holorays[user]) + LAZYREMOVE(holorays, user) + SetLightsAndPower() + return TRUE + +//Try to transfer hologram to another pad that can project on T +/obj/machinery/holopad/proc/transfer_to_nearby_pad(turf/T,mob/holo_owner) + var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] + if(!h || h.HC) //Holocalls can't change source. + return FALSE + for(var/pad in holopads) + var/obj/machinery/holopad/another = pad + if(another == src) + continue + if(another.validate_location(T)) + unset_holo(holo_owner) + if(another.masters && another.masters[holo_owner]) + another.clear_holo(holo_owner) + another.set_holo(holo_owner, h) + return TRUE + return FALSE + +/obj/machinery/holopad/proc/validate_user(mob/living/user) + if(QDELETED(user) || user.incapacitated() || !user.client) + 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/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 + else + return FALSE + +/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) + if(LAZYLEN(masters) && 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.forceMove(new_turf) + if(!transfered) + update_holoray(user,new_turf) + return TRUE + + +/obj/machinery/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) + +// RECORDED MESSAGES + +/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) + var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. + Hologram.add_overlay(record.caller_image) + Hologram.alpha = 170 + Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) + Hologram.dir = SOUTH //for now + Hologram.grant_all_languages(omnitongue=TRUE) + var/datum/language_holder/holder = Hologram.get_language_holder() + holder.selected_default_language = record.language + 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.setAnchored(TRUE)//So space wind cannot drag it. + Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click. + Hologram.set_light(2) //hologram lighting + visible_message("A holographic image of [record.caller_name] flickers to life before your eyes!") + return Hologram + +/obj/machinery/holopad/proc/replay_start() + if(!replay_mode) + replay_mode = TRUE + replay_holo = setup_replay_holo(disk.record) + temp = "Replaying...
" + temp += "Change offset
" + temp += "End replay" + SetLightsAndPower() + replay_entry(1) + return + +/obj/machinery/holopad/proc/replay_stop() + if(replay_mode) + replay_mode = FALSE + loop_mode = FALSE + offset = FALSE + temp = null + QDEL_NULL(replay_holo) + SetLightsAndPower() + updateDialog() + +/obj/machinery/holopad/proc/record_start(mob/living/user) + if(!user || !disk || disk.record) + return + disk.record = new + record_mode = TRUE + record_start = world.time + record_user = user + disk.record.set_caller_image(user) + temp = "Recording...
" + temp += "End recording." + +/obj/machinery/holopad/proc/record_message(mob/living/speaker,message,language) + if(!record_mode) + return + //make this command so you can have multiple languages in single record + if((!disk.record.caller_name || disk.record.caller_name == "Unknown") && istype(speaker)) + disk.record.caller_name = speaker.name + if(!disk.record.language) + disk.record.language = language + else if(language != disk.record.language) + disk.record.entries += list(list(HOLORECORD_LANGUAGE,language)) + + var/current_delay = 0 + for(var/E in disk.record.entries) + var/list/entry = E + if(entry[1] != HOLORECORD_DELAY) + continue + current_delay += entry[2] + + var/time_delta = world.time - record_start - current_delay + + if(time_delta >= 1) + disk.record.entries += list(list(HOLORECORD_DELAY,time_delta)) + disk.record.entries += list(list(HOLORECORD_SAY,message)) + if(disk.record.entries.len >= HOLORECORD_MAX_LENGTH) + record_stop() + +/obj/machinery/holopad/proc/replay_entry(entry_number) + if(!replay_mode) + return + if (!disk.record.entries.len) // check for zero entries such as photographs and no text recordings + return // and pretty much just display them statically untill manually stopped + if(disk.record.entries.len < entry_number) + if(loop_mode) + entry_number = 1 + else + replay_stop() + return + var/list/entry = disk.record.entries[entry_number] + var/command = entry[1] + switch(command) + if(HOLORECORD_SAY) + var/message = entry[2] + if(replay_holo) + replay_holo.say(message) + if(HOLORECORD_SOUND) + playsound(src,entry[2],50,1) + if(HOLORECORD_DELAY) + addtimer(CALLBACK(src,.proc/replay_entry,entry_number+1),entry[2]) + return + if(HOLORECORD_LANGUAGE) + var/datum/language_holder/holder = replay_holo.get_language_holder() + holder.selected_default_language = entry[2] + if(HOLORECORD_PRESET) + var/preset_type = entry[2] + var/datum/preset_holoimage/H = new preset_type + replay_holo.cut_overlays() + replay_holo.add_overlay(H.build_image()) + if(HOLORECORD_RENAME) + replay_holo.name = entry[2] + " (Hologram)" + .(entry_number+1) + +/obj/machinery/holopad/proc/record_stop() + if(record_mode) + record_mode = FALSE + temp = null + record_user = null + updateDialog() + +/obj/machinery/holopad/proc/record_clear() + if(disk && disk.record) + QDEL_NULL(disk.record) + updateDialog() + +/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 TRUE + +/obj/effect/overlay/holo_pad_hologram/examine(mob/user) + if(Impersonation) + return Impersonation.examine(user) + return ..() + +/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_TRANSPARENT + pixel_x = -32 + pixel_y = -32 + alpha = 100 + +#undef HOLOPAD_PASSIVE_POWER_USAGE +#undef HOLOGRAM_POWER_USAGE diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 4032f370..fbe16728 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -1,1087 +1,1087 @@ -/obj/mecha - name = "mecha" - desc = "Exosuit" - icon = 'icons/mecha/mecha.dmi' - density = TRUE //Dense. To raise the heat. - opacity = 1 ///opaque. Menacing. - anchored = TRUE //no pulling around. - resistance_flags = FIRE_PROOF | ACID_PROOF - layer = BELOW_MOB_LAYER//icon draw layer - infra_luminosity = 15 //byond implementation is bugged. - force = 5 - flags_1 = HEAR_1 - 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 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. - var/step_energy_drain = 10 - var/melee_energy_drain = 15 - var/overload_step_energy_drain_min = 100 - max_integrity = 300 //max_integrity is base health - var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. - 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/obj/item/stock_parts/cell/cell - var/construction_state = MECHA_LOCKED - var/list/log = new - var/last_message = 0 - var/add_req_access = 1 - var/maint_access = 0 - var/equipment_disabled = 0 //disabled due to EMP - var/dna_lock //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 = FALSE - var/lights_power = 6 - var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference - var/breach_time = 0 - var/recharge_rate = 0 - - var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. - //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/components/unary/portables_connector/connected_port = null - - var/obj/item/radio/mech/radio - 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/stepsound = 'sound/mecha/mechstep.ogg' - var/turnsound = 'sound/mecha/mechturn.ogg' - - var/melee_cooldown = 10 - var/melee_can_hit = 1 - - var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. - - var/enter_delay = 40 //Time taken to enter the mech - var/enclosed = TRUE //Set to false for open-cockpit mechs - var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI - - //Action datums - var/datum/action/innate/mecha/mech_eject/eject_action = new - var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new - var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new - var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new - var/datum/action/innate/mecha/mech_view_stats/stats_action = new - var/datum/action/innate/mecha/mech_toggle_thrusters/thrusters_action = new - var/datum/action/innate/mecha/mech_defence_mode/defense_action = new - var/datum/action/innate/mecha/mech_overload_mode/overload_action = new - var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one - var/datum/action/innate/mecha/mech_smoke/smoke_action = new - var/datum/action/innate/mecha/mech_zoom/zoom_action = new - var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new - var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new - var/datum/action/innate/mecha/strafe/strafing_action = new - - //Action vars - var/thrusters_active = FALSE - var/defence_mode = FALSE - var/defence_mode_deflect_chance = 35 - var/leg_overload_mode = FALSE - var/leg_overload_coeff = 100 - var/zoom_mode = FALSE - var/smoke = 5 - var/smoke_ready = 1 - var/smoke_cooldown = 100 - var/phasing = FALSE - var/phasing_energy_drain = 200 - var/phase_state = "" //icon_state when phasing - var/strafe = FALSE //If we are strafing - - var/nextsmash = 0 - var/smashcooldown = 3 //deciseconds - - var/occupant_sight_flags = 0 //sight flags to give to the occupant (e.g. mech mining scanner gives meson-like vision) - var/mouse_pointer - - hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) - -/obj/item/radio/mech //this has to go somewhere - -/obj/mecha/Initialize() - . = ..() - events = new - icon_state += "-open" - add_radio() - add_cabin() - if (enclosed) - 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.name] created.") - GLOB.mechas_list += src //global mech list - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/update_icon() - if (silicon_pilot && silicon_icon_state) - icon_state = silicon_icon_state - . = ..() - -/obj/mecha/get_cell() - return cell - -/obj/mecha/Destroy() - 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) - if(wreckage) - if(prob(30)) - explosion(get_turf(src), 0, 0, 1, 3) - 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) - if(internal_tank) - WR.crowbar_salvage += internal_tank - internal_tank.forceMove(WR) - else - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach(loc) - qdel(E) - if(cell) - qdel(cell) - if(internal_tank) - qdel(internal_tank) - if(AI) - AI.gib() //No wreck, no AI to recover - STOP_PROCESSING(SSobj, src) - GLOB.poi_list.Remove(src) - equipment.Cut() - cell = null - internal_tank = null - if(loc) - loc.assume_air(cabin_air) - air_update_turf() - else - qdel(cabin_air) - cabin_air = null - qdel(spark_system) - spark_system = null - qdel(smoke_system) - smoke_system = null - - GLOB.mechas_list -= src //global mech list - return ..() - -/obj/mecha/proc/restore_equipment() - equipment_disabled = 0 - if(istype(src, /obj/mecha/combat)) - mouse_pointer = 'icons/mecha/mecha_mouse.dmi' - if(occupant) - SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) - to_chat(occupant, "Equipment control unit has been rebooted successfuly.
") - occupant.update_mouse_pointer() - -/obj/mecha/CheckParts(list/parts_list) - ..() - cell = locate(/obj/item/stock_parts/cell) in contents - var/obj/item/stock_parts/scanning_module/SM = locate() in contents - var/obj/item/stock_parts/capacitor/CP = locate() in contents - if(SM) - normal_step_energy_drain = 20 - (5 * SM.rating) //10 is normal, so on lowest part its worse, on second its ok and on higher its real good up to 0 on best - step_energy_drain = normal_step_energy_drain - qdel(SM) - if(CP) - armor = armor.modifyRating(energy = (CP.rating * 10)) //Each level of capacitor protects the mech against emp by 10% - qdel(CP) - -//////////////////////// -////// Helpers ///////// -//////////////////////// - -/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.gases[/datum/gas/oxygen] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - cabin_air.gases[/datum/gas/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 = TRUE - -/obj/mecha/proc/can_use(mob/user) - if(user != occupant) - return 0 - if(user && ismob(user)) - if(!user.incapacitated()) - return 1 - return 0 - -//////////////////////////////////////////////////////////////////////////////// - -/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) - . += "[icon2html(ME, user)] \A [ME]." - if(!enclosed) - if(silicon_pilot) - to_chat(user, "[src] appears to be piloting itself...") - else if(occupant && occupant != user) //!silicon_pilot implied - to_chat(user, "You can see [occupant] inside.") - -//processing internal damage, temperature, air regulation, alert updates, lights power use. -/obj/mecha/process() - var/internal_temp_regulation = 1 - - if(internal_damage) - 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) //heat the air_contents - int_tank_air.temperature = min(6000+T0C, int_tank_air.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_TEMP_CONTROL) - internal_temp_regulation = 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.1) - 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) - - if(internal_temp_regulation) - 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))) - - if(internal_tank) - 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) - - if(occupant) - 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/lowcell, 1) - if(0.25 to 0.5) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) - if(0.01 to 0.25) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) - else - occupant.throw_alert("charge", /obj/screen/alert/emptycell) - - var/integrity = obj_integrity/max_integrity*100 - switch(integrity) - if(30 to 45) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) - if(15 to 35) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) - if(-INFINITY to 15) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) - else - occupant.clear_alert("mech damage") - var/atom/checking = occupant.loc - // recursive check to handle all cases regarding very nested occupants, - // such as brainmob inside brainitem inside MMI inside mecha - while (!isnull(checking)) - if (isturf(checking)) - // hit a turf before hitting the mecha, seems like they have - // been moved out - occupant.clear_alert("charge") - occupant.clear_alert("mech damage") - RemoveActions(occupant, human_occupant=1) - occupant = null - break - else if (checking == src) - break // all good - checking = checking.loc - - if(lights) - var/lights_energy_drain = 2 - use_power(lights_energy_drain) - -//Diagnostic HUD updates - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech - if (occupant && !enclosed && !silicon_pilot) - if (occupant.fire_stacks < 5) - occupant.fire_stacks += 1 - occupant.IgniteMob() - -/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. - return - -/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker == occupant) - if(radio?.broadcasting) - radio.talk_into(speaker, text, , spans, message_language) - //flick speech bubble - var/list/speech_bubble_recipients = list() - for(var/mob/M in get_hearers_in_view(7,src)) - if(M.client) - speech_bubble_recipients.Add(M.client) - INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) - -//////////////////////////// -///// Action processing //// -//////////////////////////// - - -/obj/mecha/proc/click_action(atom/target,mob/user,params) - if(!occupant || occupant != user ) - return - if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled - return - if(phasing) - occupant_message("Unable to interact with objects while phasing") - return - if(user.incapacitated()) - return - if(construction_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(internal_damage & MECHA_INT_CONTROL_LOST) - target = safepick(view(3,target)) - if(!target) - return - - var/mob/living/L = user - if(!Adjacent(target)) - if(selected && selected.is_ranged()) - if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) - to_chat(user, "You don't want to harm other living beings!") - return - if(selected.action(target,params)) - selected.start_cooldown() - 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 - if(selected.action(target,params)) - selected.start_cooldown() - else - if(internal_damage & MECHA_INT_CONTROL_LOST) - target = safepick(oview(1,src)) - if(!melee_can_hit || !istype(target, /atom)) - return - target.mech_melee_attack(src) - melee_can_hit = 0 - spawn(melee_cooldown) - melee_can_hit = 1 - - -/obj/mecha/proc/range_action(atom/target) - return - - -////////////////////////////////// -//////// Movement procs //////// -////////////////////////////////// - -/obj/mecha/Move(atom/newloc, direct) - . = ..() - if(.) - events.fireEvent("onMove",get_turf(src)) - if (internal_tank?.disconnect()) // Something moved us and broke connection - occupant_message("Air port connection teared off!") - log_message("Lost connection to gas port.") - -/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(internal_tank?.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(construction_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/oldloc = loc - if(internal_damage & MECHA_INT_CONTROL_LOST) - set_glide_size(DELAY_TO_GLIDE_SIZE(step_in)) - move_result = mechsteprand() - else if(dir != direction && (!strafe || occupant.client.keys_held["Alt"])) - move_result = mechturn(direction) - else - set_glide_size(DELAY_TO_GLIDE_SIZE(step_in)) - move_result = mechstep(direction) - if(move_result || loc != oldloc)// halfway done diagonal move still returns false - use_power(step_energy_drain) - can_move = world.time + step_in - return 1 - return 0 - -/obj/mecha/proc/mechturn(direction) - setDir(direction) - if(turnsound) - playsound(src,turnsound,40,1) - return 1 - -/obj/mecha/proc/mechstep(direction) - var/current_dir = dir - var/result = step(src,direction) - if(strafe) - setDir(current_dir) - if(result && stepsound) - playsound(src,stepsound,40,1) - return result - -/obj/mecha/proc/mechsteprand() - var/result = step_rand(src) - if(result && stepsound) - playsound(src,stepsound,40,1) - return result - -/obj/mecha/Bump(var/atom/obstacle) - if(phasing && get_charge() >= phasing_energy_drain && !throwing) - spawn() - if(can_move) - can_move = 0 - if(phase_state) - flick(phase_state, src) - forceMove(get_step(src,dir)) - use_power(phasing_energy_drain) - sleep(step_in*3) - can_move = 1 - else - if(..()) //mech was thrown - return - if(bumpsmash && occupant) //Need a pilot to push the PUNCH button. - if(nextsmash < world.time) - obstacle.mech_melee_attack(src) - nextsmash = world.time + smashcooldown - if(!obstacle || obstacle.CanPass(src,get_step(src,dir))) - step(src,dir) - if(isobj(obstacle)) - var/obj/O = obstacle - if(!O.anchored) - step(obstacle, dir) - else if(ismob(obstacle)) - var/mob/M = obstacle - if(!M.anchored) - 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) - return - -/obj/mecha/proc/setInternalDamage(int_dam_flag) - internal_damage |= int_dam_flag - log_append_to_last("Internal damage of type [int_dam_flag].",1) - SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) - diag_hud_set_mechstat() - return - -/obj/mecha/proc/clearInternalDamage(int_dam_flag) - if(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.") - internal_damage &= ~int_dam_flag - diag_hud_set_mechstat() - -///////////////////////////////////// -//////////// 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()]") - break - //Nothing like a big, red link to make the player feel powerful! - to_chat(user, "ASSUME DIRECT CONTROL?
") - else - examine(user) - if(occupant) - to_chat(user, "This exosuit has a pilot and cannot be controlled.") - return - var/can_control_mech = 0 - for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) - can_control_mech = 1 - to_chat(user, "[icon2html(src, user)] Status of [name]:\n[A.get_mecha_info()]") - 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(construction_state) //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 - AI.ai_restore_power()//So the AI initially has power. - AI.control_disabled = 1 - AI.radio_enabled = 0 - AI.disconnect_shell() - RemoveActions(AI, TRUE) - occupant = null - silicon_pilot = FALSE - AI.forceMove(card) - card.AI = AI - 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 = card.AI - if(!AI) - to_chat(user, "There is no AI currently installed on this device.") - return - if(AI.deployed_shell) //Recall AI if shelled so it can be checked for a client - AI.disconnect_shell() - if(AI.stat || !AI.client) - to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") - return - if(occupant || dna_lock) //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.radio_enabled = 1 - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - 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.ai_restore_power() - AI.forceMove(src) - occupant = AI - silicon_pilot = TRUE - icon_state = initial(icon_state) - update_icon() - playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',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 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) //No eject/return to core action for AI uploaded by card - else - GrantActions(AI, !AI.can_dominate_mechs) - - -//An actual AI (simple_animal mecha pilot) entering the mech -/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(pilot_mob && pilot_mob.Adjacent(src)) - if(occupant) - return - icon_state = initial(icon_state) - occupant = pilot_mob - pilot_mob.mecha = src - pilot_mob.forceMove(src) - GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob - -/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(occupant == pilot_mob) - occupant = null - if(pilot_mob.mecha == src) - pilot_mob.mecha = null - icon_state = "[initial(icon_state)]-open" - pilot_mob.forceMove(get_turf(src)) - RemoveActions(pilot_mob) - - -///////////////////////////////////// -//////// Atmospheric stuff //////// -///////////////////////////////////// - -/obj/mecha/remove_air(amount) - if(use_internal_tank) - return cabin_air.remove(amount) - return ..() - -/obj/mecha/return_air() - if(use_internal_tank) - return cabin_air - return ..() - -/obj/mecha/proc/return_pressure() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_pressure() - return - -/obj/mecha/return_temperature() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_temperature() - return - -/obj/mecha/portableConnectorReturnAir() - return internal_tank.return_air() - - -/obj/mecha/MouseDrop_T(mob/M, mob/user) - if (!user.canUseTopic(src) || (user != M)) - return - if(!ishuman(user)) // no silicons or drones in mechas. - return - log_message("[user] tries to move in.") - if (occupant) - to_chat(usr, "The [name] is already occupied!") - log_append_to_last("Permission denied.") - return - if(dna_lock) - var/passed = FALSE - if(user.has_dna()) - var/mob/living/carbon/C = user - if(C.dna.unique_enzymes==dna_lock) - passed = TRUE - if (!passed) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - log_append_to_last("Permission denied.") - return - if(!operation_allowed(user)) - to_chat(user, "Access denied. Insufficient operation keycodes.") - 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 [name].") - - if(do_after(user, enter_delay, 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!") - return - -/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) - if(H && H.client && H in range(1)) - occupant = H - H.forceMove(src) - H.update_mouse_pointer() - add_fingerprint(H) - GrantActions(H, human_occupant=1) - forceMove(loc) - log_append_to_last("[H] moved in as pilot.") - icon_state = initial(icon_state) - setDir(dir_in) - playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - return 1 - else - return 0 - -/obj/mecha/proc/mmi_move_inside(obj/item/mmi/mmi_as_oc, mob/user) - if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) - to_chat(user, "Consciousness matrix not detected!") - return FALSE - else if(mmi_as_oc.brainmob.stat) - to_chat(user, "Beta-rhythm below acceptable level!") - return FALSE - else if(occupant) - to_chat(user, "Occupant detected!") - return FALSE - else if(dna_lock && (!mmi_as_oc.brainmob.stored_dna || (dna_lock != mmi_as_oc.brainmob.stored_dna.unique_enzymes))) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - return FALSE - - visible_message("[user] starts to insert an MMI into [name].") - - 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 FALSE - -/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user) - if(!(Adjacent(mmi_as_oc) && Adjacent(user))) - return FALSE - if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) - to_chat(user, "Consciousness matrix not detected!") - return FALSE - else if(mmi_as_oc.brainmob.stat) - to_chat(user, "Beta-rhythm below acceptable level!") - return FALSE - if(!user.transferItemToLoc(mmi_as_oc, src)) - to_chat(user, "\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]!") - return FALSE - var/mob/living/brainmob = mmi_as_oc.brainmob - mmi_as_oc.mecha = src - occupant = brainmob - silicon_pilot = TRUE - brainmob.forceMove(src) //should allow relaymove - brainmob.reset_perspective(src) - brainmob.remote_control = src - brainmob.update_canmove() - brainmob.update_mouse_pointer() - icon_state = initial(icon_state) - update_icon() - setDir(dir_in) - log_message("[mmi_as_oc] moved in as pilot.") - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - GrantActions(brainmob) - return TRUE - -/obj/mecha/container_resist(mob/living/user) - go_out() - - -/obj/mecha/Exited(atom/movable/M, atom/newloc) - if(occupant && occupant == M) // The occupant exited the mech without calling go_out() - go_out(TRUE, 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("mech damage") - if(ishuman(occupant)) - mob_container = occupant - RemoveActions(occupant, human_occupant=1) - else if(isbrain(occupant)) - var/mob/living/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 - silicon_pilot = FALSE - return - else - if(!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(). - silicon_pilot = FALSE - 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.forceMove(mmi) - L.reset_perspective() - mmi.mecha = null - mmi.update_icon() - L.canmove = 0 - icon_state = initial(icon_state)+"-open" - setDir(dir_in) - - if(L && L.client) - L.update_mouse_pointer() - L.client.change_view(CONFIG_GET(string/default_view)) - zoom_mode = 0 - -///////////////////////// -////// Access stuff ///// -///////////////////////// - -/obj/mecha/proc/operation_allowed(mob/M) - req_access = operation_req_access - req_one_access = list() - return allowed(M) - -/obj/mecha/proc/internals_access_allowed(mob/M) - req_one_access = internals_req_access - req_access = list() - return allowed(M) - - - -//////////////////////////////// -/////// Messages and Log /////// -//////////////////////////////// - -/obj/mecha/proc/occupant_message(message as text) - if(message) - if(occupant && occupant.client) - to_chat(occupant, "[icon2html(src, occupant)] [message]") - return - -/obj/mecha/log_message(message as text, message_type=LOG_GAME, color=null, log_globally) - log.len++ - log[log.len] = list("time"="[STATION_TIME_TIMESTAMP("hh:mm:ss")]","date","year"="[GLOB.year_integer+540]","message"="[color?"":null][message][color?"":null]") - ..() - return log.len - -/obj/mecha/proc/log_append_to_last(message as text,red=null) - var/list/last_entry = log[log.len] - last_entry["message"] += "
[red?"":null][message][red?"":null]" - return - -GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) -GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? - -/////////////////////// -///// 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) - return 1 - return 0 - -/obj/mecha/proc/give_power(amount) - if(!isnull(get_charge())) - cell.give(amount) - return 1 - return 0 - -/obj/mecha/update_remote_sight(mob/living/user) - if(occupant_sight_flags) - if(user == occupant) - user.sight |= occupant_sight_flags +/obj/mecha + name = "mecha" + desc = "Exosuit" + icon = 'icons/mecha/mecha.dmi' + density = TRUE //Dense. To raise the heat. + opacity = 1 ///opaque. Menacing. + anchored = TRUE //no pulling around. + resistance_flags = FIRE_PROOF | ACID_PROOF + layer = BELOW_MOB_LAYER//icon draw layer + infra_luminosity = 15 //byond implementation is bugged. + force = 5 + flags_1 = HEAR_1 + 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 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. + var/step_energy_drain = 10 + var/melee_energy_drain = 15 + var/overload_step_energy_drain_min = 100 + max_integrity = 300 //max_integrity is base health + var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. + 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/obj/item/stock_parts/cell/cell + var/construction_state = MECHA_LOCKED + var/list/log = new + var/last_message = 0 + var/add_req_access = 1 + var/maint_access = 0 + var/equipment_disabled = 0 //disabled due to EMP + var/dna_lock //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 = FALSE + var/lights_power = 6 + var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference + var/breach_time = 0 + var/recharge_rate = 0 + + var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. + //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/components/unary/portables_connector/connected_port = null + + var/obj/item/radio/mech/radio + 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/stepsound = 'sound/mecha/mechstep.ogg' + var/turnsound = 'sound/mecha/mechturn.ogg' + + var/melee_cooldown = 10 + var/melee_can_hit = 1 + + var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. + + var/enter_delay = 40 //Time taken to enter the mech + var/enclosed = TRUE //Set to false for open-cockpit mechs + var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI + + //Action datums + var/datum/action/innate/mecha/mech_eject/eject_action = new + var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new + var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new + var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new + var/datum/action/innate/mecha/mech_view_stats/stats_action = new + var/datum/action/innate/mecha/mech_toggle_thrusters/thrusters_action = new + var/datum/action/innate/mecha/mech_defence_mode/defense_action = new + var/datum/action/innate/mecha/mech_overload_mode/overload_action = new + var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one + var/datum/action/innate/mecha/mech_smoke/smoke_action = new + var/datum/action/innate/mecha/mech_zoom/zoom_action = new + var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new + var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new + var/datum/action/innate/mecha/strafe/strafing_action = new + + //Action vars + var/thrusters_active = FALSE + var/defence_mode = FALSE + var/defence_mode_deflect_chance = 35 + var/leg_overload_mode = FALSE + var/leg_overload_coeff = 100 + var/zoom_mode = FALSE + var/smoke = 5 + var/smoke_ready = 1 + var/smoke_cooldown = 100 + var/phasing = FALSE + var/phasing_energy_drain = 200 + var/phase_state = "" //icon_state when phasing + var/strafe = FALSE //If we are strafing + + var/nextsmash = 0 + var/smashcooldown = 3 //deciseconds + + var/occupant_sight_flags = 0 //sight flags to give to the occupant (e.g. mech mining scanner gives meson-like vision) + var/mouse_pointer + + hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) + +/obj/item/radio/mech //this has to go somewhere + +/obj/mecha/Initialize() + . = ..() + events = new + icon_state += "-open" + add_radio() + add_cabin() + if (enclosed) + 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.name] created.") + GLOB.mechas_list += src //global mech list + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_to_hud(src) + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/update_icon() + if (silicon_pilot && silicon_icon_state) + icon_state = silicon_icon_state + . = ..() + +/obj/mecha/get_cell() + return cell + +/obj/mecha/Destroy() + 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) + if(wreckage) + if(prob(30)) + explosion(get_turf(src), 0, 0, 1, 3) + 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) + if(internal_tank) + WR.crowbar_salvage += internal_tank + internal_tank.forceMove(WR) + else + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach(loc) + qdel(E) + if(cell) + qdel(cell) + if(internal_tank) + qdel(internal_tank) + if(AI) + AI.gib() //No wreck, no AI to recover + STOP_PROCESSING(SSobj, src) + GLOB.poi_list.Remove(src) + equipment.Cut() + cell = null + internal_tank = null + if(loc) + loc.assume_air(cabin_air) + air_update_turf() + else + qdel(cabin_air) + cabin_air = null + qdel(spark_system) + spark_system = null + qdel(smoke_system) + smoke_system = null + + GLOB.mechas_list -= src //global mech list + return ..() + +/obj/mecha/proc/restore_equipment() + equipment_disabled = 0 + if(istype(src, /obj/mecha/combat)) + mouse_pointer = 'icons/mecha/mecha_mouse.dmi' + if(occupant) + SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) + to_chat(occupant, "Equipment control unit has been rebooted successfuly.
") + occupant.update_mouse_pointer() + +/obj/mecha/CheckParts(list/parts_list) + ..() + cell = locate(/obj/item/stock_parts/cell) in contents + var/obj/item/stock_parts/scanning_module/SM = locate() in contents + var/obj/item/stock_parts/capacitor/CP = locate() in contents + if(SM) + normal_step_energy_drain = 20 - (5 * SM.rating) //10 is normal, so on lowest part its worse, on second its ok and on higher its real good up to 0 on best + step_energy_drain = normal_step_energy_drain + qdel(SM) + if(CP) + armor = armor.modifyRating(energy = (CP.rating * 10)) //Each level of capacitor protects the mech against emp by 10% + qdel(CP) + +//////////////////////// +////// Helpers ///////// +//////////////////////// + +/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.gases[/datum/gas/oxygen] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.gases[/datum/gas/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 = TRUE + +/obj/mecha/proc/can_use(mob/user) + if(user != occupant) + return 0 + if(user && ismob(user)) + if(!user.incapacitated()) + return 1 + return 0 + +//////////////////////////////////////////////////////////////////////////////// + +/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) + . += "[icon2html(ME, user)] \A [ME]." + if(!enclosed) + if(silicon_pilot) + to_chat(user, "[src] appears to be piloting itself...") + else if(occupant && occupant != user) //!silicon_pilot implied + to_chat(user, "You can see [occupant] inside.") + +//processing internal damage, temperature, air regulation, alert updates, lights power use. +/obj/mecha/process() + var/internal_temp_regulation = 1 + + if(internal_damage) + 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) //heat the air_contents + int_tank_air.temperature = min(6000+T0C, int_tank_air.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_TEMP_CONTROL) + internal_temp_regulation = 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.1) + 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) + + if(internal_temp_regulation) + 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))) + + if(internal_tank) + 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) + + if(occupant) + 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/lowcell, 1) + if(0.25 to 0.5) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) + if(0.01 to 0.25) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) + else + occupant.throw_alert("charge", /obj/screen/alert/emptycell) + + var/integrity = obj_integrity/max_integrity*100 + switch(integrity) + if(30 to 45) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) + if(15 to 35) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) + if(-INFINITY to 15) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) + else + occupant.clear_alert("mech damage") + var/atom/checking = occupant.loc + // recursive check to handle all cases regarding very nested occupants, + // such as brainmob inside brainitem inside MMI inside mecha + while (!isnull(checking)) + if (isturf(checking)) + // hit a turf before hitting the mecha, seems like they have + // been moved out + occupant.clear_alert("charge") + occupant.clear_alert("mech damage") + RemoveActions(occupant, human_occupant=1) + occupant = null + break + else if (checking == src) + break // all good + checking = checking.loc + + if(lights) + var/lights_energy_drain = 2 + use_power(lights_energy_drain) + +//Diagnostic HUD updates + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech + if (occupant && !enclosed && !silicon_pilot) + if (occupant.fire_stacks < 5) + occupant.fire_stacks += 1 + occupant.IgniteMob() + +/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. + return + +/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(speaker == occupant) + if(radio?.broadcasting) + radio.talk_into(speaker, text, , spans, message_language) + //flick speech bubble + var/list/speech_bubble_recipients = list() + for(var/mob/M in get_hearers_in_view(7,src)) + if(M.client) + speech_bubble_recipients.Add(M.client) + INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) + +//////////////////////////// +///// Action processing //// +//////////////////////////// + + +/obj/mecha/proc/click_action(atom/target,mob/user,params) + if(!occupant || occupant != user ) + return + if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled + return + if(phasing) + occupant_message("Unable to interact with objects while phasing") + return + if(user.incapacitated()) + return + if(construction_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(internal_damage & MECHA_INT_CONTROL_LOST) + target = safepick(view(3,target)) + if(!target) + return + + var/mob/living/L = user + if(!Adjacent(target)) + if(selected && selected.is_ranged()) + if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) + to_chat(user, "You don't want to harm other living beings!") + return + if(selected.action(target,params)) + selected.start_cooldown() + 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 + if(selected.action(target,params)) + selected.start_cooldown() + else + if(internal_damage & MECHA_INT_CONTROL_LOST) + target = safepick(oview(1,src)) + if(!melee_can_hit || !istype(target, /atom)) + return + target.mech_melee_attack(src) + melee_can_hit = 0 + spawn(melee_cooldown) + melee_can_hit = 1 + + +/obj/mecha/proc/range_action(atom/target) + return + + +////////////////////////////////// +//////// Movement procs //////// +////////////////////////////////// + +/obj/mecha/Move(atom/newloc, direct) + . = ..() + if(.) + events.fireEvent("onMove",get_turf(src)) + if (internal_tank?.disconnect()) // Something moved us and broke connection + occupant_message("Air port connection teared off!") + log_message("Lost connection to gas port.") + +/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(internal_tank?.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(construction_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/oldloc = loc + if(internal_damage & MECHA_INT_CONTROL_LOST) + set_glide_size(DELAY_TO_GLIDE_SIZE(step_in)) + move_result = mechsteprand() + else if(dir != direction && (!strafe || occupant.client.keys_held["Alt"])) + move_result = mechturn(direction) + else + set_glide_size(DELAY_TO_GLIDE_SIZE(step_in)) + move_result = mechstep(direction) + if(move_result || loc != oldloc)// halfway done diagonal move still returns false + use_power(step_energy_drain) + can_move = world.time + step_in + return 1 + return 0 + +/obj/mecha/proc/mechturn(direction) + setDir(direction) + if(turnsound) + playsound(src,turnsound,40,1) + return 1 + +/obj/mecha/proc/mechstep(direction) + var/current_dir = dir + var/result = step(src,direction) + if(strafe) + setDir(current_dir) + if(result && stepsound) + playsound(src,stepsound,40,1) + return result + +/obj/mecha/proc/mechsteprand() + var/result = step_rand(src) + if(result && stepsound) + playsound(src,stepsound,40,1) + return result + +/obj/mecha/Bump(var/atom/obstacle) + if(phasing && get_charge() >= phasing_energy_drain && !throwing) + spawn() + if(can_move) + can_move = 0 + if(phase_state) + flick(phase_state, src) + forceMove(get_step(src,dir)) + use_power(phasing_energy_drain) + sleep(step_in*3) + can_move = 1 + else + if(..()) //mech was thrown + return + if(bumpsmash && occupant) //Need a pilot to push the PUNCH button. + if(nextsmash < world.time) + obstacle.mech_melee_attack(src) + nextsmash = world.time + smashcooldown + if(!obstacle || obstacle.CanPass(src,get_step(src,dir))) + step(src,dir) + if(isobj(obstacle)) + var/obj/O = obstacle + if(!O.anchored) + step(obstacle, dir) + else if(ismob(obstacle)) + var/mob/M = obstacle + if(!M.anchored) + 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) + return + +/obj/mecha/proc/setInternalDamage(int_dam_flag) + internal_damage |= int_dam_flag + log_append_to_last("Internal damage of type [int_dam_flag].",1) + SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) + diag_hud_set_mechstat() + return + +/obj/mecha/proc/clearInternalDamage(int_dam_flag) + if(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.") + internal_damage &= ~int_dam_flag + diag_hud_set_mechstat() + +///////////////////////////////////// +//////////// 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()]") + break + //Nothing like a big, red link to make the player feel powerful! + to_chat(user, "ASSUME DIRECT CONTROL?
") + else + examine(user) + if(occupant) + to_chat(user, "This exosuit has a pilot and cannot be controlled.") + return + var/can_control_mech = 0 + for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) + can_control_mech = 1 + to_chat(user, "[icon2html(src, user)] Status of [name]:\n[A.get_mecha_info()]") + 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(construction_state) //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 + AI.ai_restore_power()//So the AI initially has power. + AI.control_disabled = 1 + AI.radio_enabled = 0 + AI.disconnect_shell() + RemoveActions(AI, TRUE) + occupant = null + silicon_pilot = FALSE + AI.forceMove(card) + card.AI = AI + 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 = card.AI + if(!AI) + to_chat(user, "There is no AI currently installed on this device.") + return + if(AI.deployed_shell) //Recall AI if shelled so it can be checked for a client + AI.disconnect_shell() + if(AI.stat || !AI.client) + to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") + return + if(occupant || dna_lock) //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.radio_enabled = 1 + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + 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.ai_restore_power() + AI.forceMove(src) + occupant = AI + silicon_pilot = TRUE + icon_state = initial(icon_state) + update_icon() + playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',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 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) //No eject/return to core action for AI uploaded by card + else + GrantActions(AI, !AI.can_dominate_mechs) + + +//An actual AI (simple_animal mecha pilot) entering the mech +/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(pilot_mob && pilot_mob.Adjacent(src)) + if(occupant) + return + icon_state = initial(icon_state) + occupant = pilot_mob + pilot_mob.mecha = src + pilot_mob.forceMove(src) + GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob + +/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(occupant == pilot_mob) + occupant = null + if(pilot_mob.mecha == src) + pilot_mob.mecha = null + icon_state = "[initial(icon_state)]-open" + pilot_mob.forceMove(get_turf(src)) + RemoveActions(pilot_mob) + + +///////////////////////////////////// +//////// Atmospheric stuff //////// +///////////////////////////////////// + +/obj/mecha/remove_air(amount) + if(use_internal_tank) + return cabin_air.remove(amount) + return ..() + +/obj/mecha/return_air() + if(use_internal_tank) + return cabin_air + return ..() + +/obj/mecha/proc/return_pressure() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_pressure() + return + +/obj/mecha/return_temperature() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_temperature() + return + +/obj/mecha/portableConnectorReturnAir() + return internal_tank.return_air() + + +/obj/mecha/MouseDrop_T(mob/M, mob/user) + if (!user.canUseTopic(src) || (user != M)) + return + if(!ishuman(user)) // no silicons or drones in mechas. + return + log_message("[user] tries to move in.") + if (occupant) + to_chat(usr, "The [name] is already occupied!") + log_append_to_last("Permission denied.") + return + if(dna_lock) + var/passed = FALSE + if(user.has_dna()) + var/mob/living/carbon/C = user + if(C.dna.unique_enzymes==dna_lock) + passed = TRUE + if (!passed) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + log_append_to_last("Permission denied.") + return + if(!operation_allowed(user)) + to_chat(user, "Access denied. Insufficient operation keycodes.") + 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 [name].") + + if(do_after(user, enter_delay, 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!") + return + +/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) + if(H && H.client && H in range(1)) + occupant = H + H.forceMove(src) + H.update_mouse_pointer() + add_fingerprint(H) + GrantActions(H, human_occupant=1) + forceMove(loc) + log_append_to_last("[H] moved in as pilot.") + icon_state = initial(icon_state) + setDir(dir_in) + playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + return 1 + else + return 0 + +/obj/mecha/proc/mmi_move_inside(obj/item/mmi/mmi_as_oc, mob/user) + if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) + to_chat(user, "Consciousness matrix not detected!") + return FALSE + else if(mmi_as_oc.brainmob.stat) + to_chat(user, "Beta-rhythm below acceptable level!") + return FALSE + else if(occupant) + to_chat(user, "Occupant detected!") + return FALSE + else if(dna_lock && (!mmi_as_oc.brainmob.stored_dna || (dna_lock != mmi_as_oc.brainmob.stored_dna.unique_enzymes))) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + return FALSE + + visible_message("[user] starts to insert an MMI into [name].") + + 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 FALSE + +/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user) + if(!(Adjacent(mmi_as_oc) && Adjacent(user))) + return FALSE + if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) + to_chat(user, "Consciousness matrix not detected!") + return FALSE + else if(mmi_as_oc.brainmob.stat) + to_chat(user, "Beta-rhythm below acceptable level!") + return FALSE + if(!user.transferItemToLoc(mmi_as_oc, src)) + to_chat(user, "\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]!") + return FALSE + var/mob/living/brainmob = mmi_as_oc.brainmob + mmi_as_oc.mecha = src + occupant = brainmob + silicon_pilot = TRUE + brainmob.forceMove(src) //should allow relaymove + brainmob.reset_perspective(src) + brainmob.remote_control = src + brainmob.update_canmove() + brainmob.update_mouse_pointer() + icon_state = initial(icon_state) + update_icon() + setDir(dir_in) + log_message("[mmi_as_oc] moved in as pilot.") + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + GrantActions(brainmob) + return TRUE + +/obj/mecha/container_resist(mob/living/user) + go_out() + + +/obj/mecha/Exited(atom/movable/M, atom/newloc) + if(occupant && occupant == M) // The occupant exited the mech without calling go_out() + go_out(TRUE, 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("mech damage") + if(ishuman(occupant)) + mob_container = occupant + RemoveActions(occupant, human_occupant=1) + else if(isbrain(occupant)) + var/mob/living/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 + silicon_pilot = FALSE + return + else + if(!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(). + silicon_pilot = FALSE + 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.forceMove(mmi) + L.reset_perspective() + mmi.mecha = null + mmi.update_icon() + L.canmove = 0 + icon_state = initial(icon_state)+"-open" + setDir(dir_in) + + if(L && L.client) + L.update_mouse_pointer() + L.client.change_view(CONFIG_GET(string/default_view)) + zoom_mode = 0 + +///////////////////////// +////// Access stuff ///// +///////////////////////// + +/obj/mecha/proc/operation_allowed(mob/M) + req_access = operation_req_access + req_one_access = list() + return allowed(M) + +/obj/mecha/proc/internals_access_allowed(mob/M) + req_one_access = internals_req_access + req_access = list() + return allowed(M) + + + +//////////////////////////////// +/////// Messages and Log /////// +//////////////////////////////// + +/obj/mecha/proc/occupant_message(message as text) + if(message) + if(occupant && occupant.client) + to_chat(occupant, "[icon2html(src, occupant)] [message]") + return + +/obj/mecha/log_message(message as text, message_type=LOG_GAME, color=null, log_globally) + log.len++ + log[log.len] = list("time"="[STATION_TIME_TIMESTAMP("hh:mm:ss")]","date","year"="[GLOB.year_integer+540]","message"="[color?"":null][message][color?"":null]") + ..() + return log.len + +/obj/mecha/proc/log_append_to_last(message as text,red=null) + var/list/last_entry = log[log.len] + last_entry["message"] += "
[red?"":null][message][red?"":null]" + return + +GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) +GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? + +/////////////////////// +///// 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) + return 1 + return 0 + +/obj/mecha/proc/give_power(amount) + if(!isnull(get_charge())) + cell.give(amount) + return 1 + return 0 + +/obj/mecha/update_remote_sight(mob/living/user) + if(occupant_sight_flags) + if(user == occupant) + user.sight |= occupant_sight_flags diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index 7be28be0..efdf0557 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -1,150 +1,150 @@ -/obj/item/radio/intercom - name = "station intercom" - desc = "Talk through this." - icon_state = "intercom" - anchored = TRUE - w_class = WEIGHT_CLASS_BULKY - canhear_range = 2 - var/number = 0 - var/anyai = 1 - var/mob/living/silicon/ai/ai = list() - var/last_tick //used to delay the powercheck - dog_fashion = null - var/unfastened = FALSE - -/obj/item/radio/intercom/unscrewed - unfastened = TRUE - -/obj/item/radio/intercom/ratvar - name = "hierophant intercom" - desc = "A modified intercom that uses the Hierophant network instead of subspace tech. Can listen to and broadcast on any frequency." - icon_state = "intercom_ratvar" - freerange = TRUE - -/obj/item/radio/intercom/ratvar/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/screwdriver)) - to_chat(user, "[src] is fastened to the wall with [is_servant_of_ratvar(user) ? "replicant alloy" : "some material you've never seen"], and can't be removed.") - return //no unfastening! - . = ..() - -/obj/item/radio/intercom/ratvar/process() - if(!istype(SSticker.mode, /datum/game_mode/clockwork_cult)) - invisibility = INVISIBILITY_OBSERVER - alpha = 125 - emped = TRUE - else - invisibility = initial(invisibility) - alpha = initial(alpha) - emped = FALSE - ..() - -/obj/item/radio/intercom/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - START_PROCESSING(SSobj, src) - -/obj/item/radio/intercom/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/radio/intercom/examine(mob/user) - . = ..() - if(!unfastened) - . += "It's screwed and secured to the wall." - else - . += "It's unscrewed from the wall, and can be detached." - -/obj/item/radio/intercom/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/screwdriver)) - if(unfastened) - user.visible_message("[user] starts tightening [src]'s screws...", "You start screwing in [src]...") - if(I.use_tool(src, user, 30, volume=50)) - user.visible_message("[user] tightens [src]'s screws!", "You tighten [src]'s screws.") - unfastened = FALSE - else - user.visible_message("[user] starts loosening [src]'s screws...", "You start unscrewing [src]...") - if(I.use_tool(src, user, 40, volume=50)) - user.visible_message("[user] loosens [src]'s screws!", "You unscrew [src], loosening it from the wall.") - unfastened = TRUE - return - else if(istype(I, /obj/item/wrench)) - if(!unfastened) - to_chat(user, "You need to unscrew [src] from the wall first!") - return - user.visible_message("[user] starts unsecuring [src]...", "You start unsecuring [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 80)) - user.visible_message("[user] unsecures [src]!", "You detach [src] from the wall.") - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - new/obj/item/wallframe/intercom(get_turf(src)) - qdel(src) - return - return ..() - -/obj/item/radio/intercom/attack_ai(mob/user) - interact(user) - -/obj/item/radio/intercom/attack_hand(mob/user) - . = ..() - if(.) - return - interact(user) - -/obj/item/radio/intercom/interact(mob/user) - ..() - ui_interact(user, state = GLOB.default_state) - -/obj/item/radio/intercom/can_receive(freq, level) - if(!on) - return FALSE - if(wires.is_cut(WIRE_RX)) - return FALSE - if(!(0 in level)) - var/turf/position = get_turf(src) - if(isnull(position) || !(position.z in level)) - return FALSE - if(!src.listening) - return FALSE - if(freq == FREQ_SYNDICATE) - if(!(src.syndie)) - return FALSE//Prevents broadcast of messages over devices lacking the encryption - - return TRUE - - -/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if (message_mode == MODE_INTERCOM) - return // Avoid hearing the same thing twice - if(!anyai && !(speaker in ai)) - return - ..() - -/obj/item/radio/intercom/process() - if(((world.timeofday - last_tick) > 30) || ((world.timeofday - last_tick) < 0)) - last_tick = world.timeofday - - var/area/A = get_area(src) - if(!A || emped) - on = FALSE - else - on = A.powered(EQUIP) // set "on" to the power status - - if(!on) - icon_state = "intercom-p" - else - icon_state = initial(icon_state) - -/obj/item/radio/intercom/add_blood_DNA(list/blood_dna) - return FALSE - -//Created through the autolathe or through deconstructing intercoms. Can be applied to wall to make a new intercom on it! -/obj/item/wallframe/intercom - name = "intercom frame" - desc = "A ready-to-go intercom. Just slap it on a wall and screw it in!" - icon_state = "intercom" - result_path = /obj/item/radio/intercom/unscrewed - pixel_shift = 29 - inverse = TRUE - materials = list(MAT_METAL = 75, MAT_GLASS = 25) +/obj/item/radio/intercom + name = "station intercom" + desc = "Talk through this." + icon_state = "intercom" + anchored = TRUE + w_class = WEIGHT_CLASS_BULKY + canhear_range = 2 + var/number = 0 + var/anyai = 1 + var/mob/living/silicon/ai/ai = list() + var/last_tick //used to delay the powercheck + dog_fashion = null + var/unfastened = FALSE + +/obj/item/radio/intercom/unscrewed + unfastened = TRUE + +/obj/item/radio/intercom/ratvar + name = "hierophant intercom" + desc = "A modified intercom that uses the Hierophant network instead of subspace tech. Can listen to and broadcast on any frequency." + icon_state = "intercom_ratvar" + freerange = TRUE + +/obj/item/radio/intercom/ratvar/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/screwdriver)) + to_chat(user, "[src] is fastened to the wall with [is_servant_of_ratvar(user) ? "replicant alloy" : "some material you've never seen"], and can't be removed.") + return //no unfastening! + . = ..() + +/obj/item/radio/intercom/ratvar/process() + if(!istype(SSticker.mode, /datum/game_mode/clockwork_cult)) + invisibility = INVISIBILITY_OBSERVER + alpha = 125 + emped = TRUE + else + invisibility = initial(invisibility) + alpha = initial(alpha) + emped = FALSE + ..() + +/obj/item/radio/intercom/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + START_PROCESSING(SSobj, src) + +/obj/item/radio/intercom/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/radio/intercom/examine(mob/user) + . = ..() + if(!unfastened) + . += "It's screwed and secured to the wall." + else + . += "It's unscrewed from the wall, and can be detached." + +/obj/item/radio/intercom/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/screwdriver)) + if(unfastened) + user.visible_message("[user] starts tightening [src]'s screws...", "You start screwing in [src]...") + if(I.use_tool(src, user, 30, volume=50)) + user.visible_message("[user] tightens [src]'s screws!", "You tighten [src]'s screws.") + unfastened = FALSE + else + user.visible_message("[user] starts loosening [src]'s screws...", "You start unscrewing [src]...") + if(I.use_tool(src, user, 40, volume=50)) + user.visible_message("[user] loosens [src]'s screws!", "You unscrew [src], loosening it from the wall.") + unfastened = TRUE + return + else if(istype(I, /obj/item/wrench)) + if(!unfastened) + to_chat(user, "You need to unscrew [src] from the wall first!") + return + user.visible_message("[user] starts unsecuring [src]...", "You start unsecuring [src]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 80)) + user.visible_message("[user] unsecures [src]!", "You detach [src] from the wall.") + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + new/obj/item/wallframe/intercom(get_turf(src)) + qdel(src) + return + return ..() + +/obj/item/radio/intercom/attack_ai(mob/user) + interact(user) + +/obj/item/radio/intercom/attack_hand(mob/user) + . = ..() + if(.) + return + interact(user) + +/obj/item/radio/intercom/interact(mob/user) + ..() + ui_interact(user, state = GLOB.default_state) + +/obj/item/radio/intercom/can_receive(freq, level) + if(!on) + return FALSE + if(wires.is_cut(WIRE_RX)) + return FALSE + if(!(0 in level)) + var/turf/position = get_turf(src) + if(isnull(position) || !(position.z in level)) + return FALSE + if(!src.listening) + return FALSE + if(freq == FREQ_SYNDICATE) + if(!(src.syndie)) + return FALSE//Prevents broadcast of messages over devices lacking the encryption + + return TRUE + + +/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if (message_mode == MODE_INTERCOM) + return // Avoid hearing the same thing twice + if(!anyai && !(speaker in ai)) + return + ..() + +/obj/item/radio/intercom/process() + if(((world.timeofday - last_tick) > 30) || ((world.timeofday - last_tick) < 0)) + last_tick = world.timeofday + + var/area/A = get_area(src) + if(!A || emped) + on = FALSE + else + on = A.powered(EQUIP) // set "on" to the power status + + if(!on) + icon_state = "intercom-p" + else + icon_state = initial(icon_state) + +/obj/item/radio/intercom/add_blood_DNA(list/blood_dna) + return FALSE + +//Created through the autolathe or through deconstructing intercoms. Can be applied to wall to make a new intercom on it! +/obj/item/wallframe/intercom + name = "intercom frame" + desc = "A ready-to-go intercom. Just slap it on a wall and screw it in!" + icon_state = "intercom" + result_path = /obj/item/radio/intercom/unscrewed + pixel_shift = 29 + inverse = TRUE + materials = list(MAT_METAL = 75, MAT_GLASS = 25) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 8c300360..7823e6bf 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -1,428 +1,428 @@ -/obj/item/radio - icon = 'icons/obj/radio.dmi' - name = "station bounced radio" - icon_state = "walkietalkie" - item_state = "walkietalkie" - desc = "A basic handheld radio that communicates with local telecommunication networks." - dog_fashion = /datum/dog_fashion/back - - flags_1 = CONDUCT_1 | HEAR_1 - slot_flags = ITEM_SLOT_BELT - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=75, MAT_GLASS=25) - obj_flags = USES_TGUI - - var/on = TRUE - var/frequency = FREQ_COMMON - var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives. - var/emped = 0 // Tracks the number of EMPs currently stacked. - - var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby. - var/listening = TRUE // Whether the radio is currently receiving. - var/prison_radio = FALSE // If true, the transmit wire starts cut. - var/unscrewed = FALSE // Whether wires are accessible. Toggleable by screwdrivering. - var/freerange = FALSE // If true, the radio has access to the full spectrum. - var/subspace_transmission = FALSE // If true, the radio transmits and receives on subspace exclusively. - var/subspace_switchable = FALSE // If true, subspace_transmission can be toggled at will. - var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios. - var/use_command = FALSE // If true, broadcasts will be large and BOLD. - var/command = FALSE // If true, use_command can be toggled at will. - - // Encryption key handling - var/obj/item/encryptionkey/keyslot - var/translate_binary = FALSE // If true, can hear the special binary channel. - var/independent = FALSE // If true, can say/hear on the special CentCom channel. - var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel. - var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h). - var/list/secure_radio_connections - - var/const/FREQ_LISTENING = 1 - //FREQ_BROADCASTING = 2 - -/obj/item/radio/suicide_act(mob/living/user) - user.visible_message("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/radio/proc/set_frequency(new_frequency) - SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args) - remove_radio(src, frequency) - frequency = add_radio(src, new_frequency) - -/obj/item/radio/proc/recalculateChannels() - channels = list() - translate_binary = FALSE - syndie = FALSE - independent = FALSE - - if(keyslot) - for(var/ch_name in keyslot.channels) - if(!(ch_name in channels)) - channels[ch_name] = keyslot.channels[ch_name] - - if(keyslot.translate_binary) - translate_binary = TRUE - if(keyslot.syndie) - syndie = TRUE - if(keyslot.independent) - independent = TRUE - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/proc/make_syndie() // Turns normal radios into Syndicate radios! - qdel(keyslot) - keyslot = new /obj/item/encryptionkey/syndicate - syndie = 1 - recalculateChannels() - -/obj/item/radio/Destroy() - remove_radio_all(src) //Just to be sure - QDEL_NULL(wires) - QDEL_NULL(keyslot) - return ..() - -/obj/item/radio/Initialize() - wires = new /datum/wires/radio(src) - if(prison_radio) - wires.cut(WIRE_TX) // OH GOD WHY - secure_radio_connections = new - . = ..() - frequency = sanitize_frequency(frequency, freerange) - set_frequency(frequency) - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) - -/obj/item/radio/interact(mob/user) - if(unscrewed && !isAI(user)) - wires.interact(user) - add_fingerprint(user) - else - ..() - -/obj/item/radio/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state) - . = ..() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "radio", name, 370, 220 + channels.len * 22, master_ui, state) - ui.open() - -/obj/item/radio/ui_data(mob/user) - var/list/data = list() - - data["broadcasting"] = broadcasting - data["listening"] = listening - data["frequency"] = frequency - data["minFrequency"] = freerange ? MIN_FREE_FREQ : MIN_FREQ - data["maxFrequency"] = freerange ? MAX_FREE_FREQ : MAX_FREQ - data["freqlock"] = freqlock - data["channels"] = list() - for(var/channel in channels) - data["channels"][channel] = channels[channel] & FREQ_LISTENING - data["command"] = command - data["useCommand"] = use_command - data["subspace"] = subspace_transmission - data["subspaceSwitchable"] = subspace_switchable - data["headset"] = istype(src, /obj/item/radio/headset) - - return data - -/obj/item/radio/ui_act(action, params, datum/tgui/ui) - if(..()) - return - switch(action) - if("frequency") - if(freqlock) - return - var/tune = params["tune"] - var/adjust = text2num(params["adjust"]) - if(tune == "input") - var/min = format_frequency(freerange ? MIN_FREE_FREQ : MIN_FREQ) - var/max = format_frequency(freerange ? MAX_FREE_FREQ : MAX_FREQ) - tune = input("Tune frequency ([min]-[max]):", name, format_frequency(frequency)) as null|num - if(!isnull(tune) && !..()) - if (tune < MIN_FREE_FREQ && tune <= MAX_FREE_FREQ / 10) - // allow typing 144.7 to get 1447 - tune *= 10 - . = TRUE - else if(adjust) - tune = frequency + adjust * 10 - . = TRUE - else if(text2num(tune) != null) - tune = tune * 10 - . = TRUE - if(.) - set_frequency(sanitize_frequency(tune, freerange)) - if("listen") - listening = !listening - . = TRUE - if("broadcast") - broadcasting = !broadcasting - . = TRUE - if("channel") - var/channel = params["channel"] - if(!(channel in channels)) - return - if(channels[channel] & FREQ_LISTENING) - channels[channel] &= ~FREQ_LISTENING - else - channels[channel] |= FREQ_LISTENING - . = TRUE - if("command") - use_command = !use_command - . = TRUE - if("subspace") - if(subspace_switchable) - subspace_transmission = !subspace_transmission - if(!subspace_transmission) - channels = list() - else - recalculateChannels() - . = TRUE - -/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language) - if(!spans) - spans = list(M.speech_span) - if(!language) - language = M.get_default_language() - INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language) - return ITALICS | REDUCE_RANGE - -/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language) - if(!on) - return // the device has to be on - if(!M || !message) - return - if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios - return - if(!M.IsVocal()) - return - - if(use_command) - spans |= SPAN_COMMAND - - /* - Roughly speaking, radios attempt to make a subspace transmission (which - is received, processed, and rebroadcast by the telecomms satellite) and - if that fails, they send a mundane radio transmission. - - Headsets cannot send/receive mundane transmissions, only subspace. - Syndicate radios can hear transmissions on all well-known frequencies. - CentCom radios can hear the CentCom frequency no matter what. - */ - - // From the channel, determine the frequency and get a reference to it. - var/freq - if(channel && channels && channels.len > 0) - if(channel == MODE_DEPARTMENT) - channel = channels[1] - freq = secure_radio_connections[channel] - if (!channels[channel]) // if the channel is turned off, don't broadcast - return - else - freq = frequency - channel = null - - // Nearby active jammers severely gibberish the message - var/turf/position = get_turf(src) - for(var/obj/item/jammer/jammer in GLOB.active_jammers) - var/turf/jammer_turf = get_turf(jammer) - if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) < jammer.range)) - message = Gibberish(message,100) - break - - // Determine the identity information which will be attached to the signal. - var/atom/movable/virtualspeaker/speaker = new(null, M, src) - - // Construct the signal - var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) - - // Independent radios, on the CentCom frequency, reach all independent radios - if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_SUPERSPACE - signal.levels = list(0) // reaches all Z-levels - signal.broadcast() - return - - // All radios make an attempt to use the subspace system first - signal.send_to_receivers() - - // If the radio is subspace-only, that's all it can do - if (subspace_transmission) - return - - // Non-subspace radios will check in a couple of seconds, and if the signal - // was never received, send a mundane broadcast (no headsets). - addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) - -/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) - var/turf/T = get_turf(src) - if (signal.data["done"] && (T.z in signal.levels)) - return - - // Okay, the signal was never processed, send a mundane broadcast. - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_RADIO - signal.levels = list(T.z) - signal.broadcast() - -/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) - return - - if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) - // radios don't pick up whispers very well - raw_message = stars(raw_message) - else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) - // try to avoid being heard double - if (loc == speaker && ismob(speaker)) - var/mob/M = speaker - var/idx = M.get_held_index_of_item(src) - // left hands are odd slots - if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) - return - - talk_into(speaker, raw_message, , spans, language=message_language) - -// Checks if this radio can receive on the given frequency. -/obj/item/radio/proc/can_receive(freq, level) - // deny checks - if (!on || !listening || wires.is_cut(WIRE_RX)) - return FALSE - if (freq == FREQ_SYNDICATE && !syndie) - return FALSE - if (freq == FREQ_CENTCOM) - return independent // hard-ignores the z-level check - if (!(0 in level)) - var/turf/position = get_turf(src) - if(!position || !(position.z in level)) - return FALSE - - // allow checks: are we listening on that frequency? - if (freq == frequency) - return TRUE - for(var/ch_name in channels) - if(channels[ch_name] & FREQ_LISTENING) - //the GLOB.radiochannels list is located in communications.dm - if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) - return TRUE - return FALSE - - -/obj/item/radio/examine(mob/user) - . = ..() - if (unscrewed) - . += "It can be attached and modified." - else - . += "It cannot be modified or attached." - -/obj/item/radio/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(istype(W, /obj/item/screwdriver)) - unscrewed = !unscrewed - if(unscrewed) - to_chat(user, "The radio can now be attached and modified!") - else - to_chat(user, "The radio can no longer be modified or attached!") - else - return ..() - -/obj/item/radio/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - emped++ //There's been an EMP; better count it - var/curremp = emped //Remember which EMP this was - if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice - to_chat(loc, "\The [src] overloads.") - broadcasting = FALSE - listening = FALSE - for (var/ch_name in channels) - channels[ch_name] = 0 - on = FALSE - spawn(200) - if(emped == curremp) //Don't fix it if it's been EMP'd again - emped = 0 - if (!istype(src, /obj/item/radio/intercom)) // intercoms will turn back on on their own - on = TRUE - -/////////////////////////////// -//////////Borg Radios////////// -/////////////////////////////// -//Giving borgs their own radio to have some more room to work with -Sieve - -/obj/item/radio/borg - name = "cyborg radio" - subspace_switchable = TRUE - dog_fashion = null - -/obj/item/radio/borg/Initialize(mapload) - . = ..() - -/obj/item/radio/borg/syndicate - syndie = 1 - keyslot = new /obj/item/encryptionkey/syndicate - -/obj/item/radio/borg/syndicate/Initialize() - . = ..() - set_frequency(FREQ_SYNDICATE) - -/obj/item/radio/borg/attackby(obj/item/W, mob/user, params) - - if(istype(W, /obj/item/screwdriver)) - if(keyslot) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - - if(keyslot) - var/turf/T = get_turf(user) - if(T) - keyslot.forceMove(T) - keyslot = null - - recalculateChannels() - to_chat(user, "You pop out the encryption key in the radio.") - - else - to_chat(user, "This radio doesn't have any encryption keys!") - - else if(istype(W, /obj/item/encryptionkey/)) - if(keyslot) - to_chat(user, "The radio can't hold another key!") - return - - if(!keyslot) - if(!user.transferItemToLoc(W, src)) - return - keyslot = W - - recalculateChannels() - - -/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. - listening = 0 // And it's nice to have a subtype too for future features. - dog_fashion = /datum/dog_fashion/back - -/obj/item/radio/internal - var/obj/item/implant/radio/implant - -/obj/item/radio/internal/Initialize(mapload, obj/item/implant/radio/_implant) - . = ..() - implant = _implant - -/obj/item/radio/internal/Destroy() - if(implant?.imp_in) - qdel(implant) - else - return ..() +/obj/item/radio + icon = 'icons/obj/radio.dmi' + name = "station bounced radio" + icon_state = "walkietalkie" + item_state = "walkietalkie" + desc = "A basic handheld radio that communicates with local telecommunication networks." + dog_fashion = /datum/dog_fashion/back + + flags_1 = CONDUCT_1 | HEAR_1 + slot_flags = ITEM_SLOT_BELT + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=75, MAT_GLASS=25) + obj_flags = USES_TGUI + + var/on = TRUE + var/frequency = FREQ_COMMON + var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives. + var/emped = 0 // Tracks the number of EMPs currently stacked. + + var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby. + var/listening = TRUE // Whether the radio is currently receiving. + var/prison_radio = FALSE // If true, the transmit wire starts cut. + var/unscrewed = FALSE // Whether wires are accessible. Toggleable by screwdrivering. + var/freerange = FALSE // If true, the radio has access to the full spectrum. + var/subspace_transmission = FALSE // If true, the radio transmits and receives on subspace exclusively. + var/subspace_switchable = FALSE // If true, subspace_transmission can be toggled at will. + var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios. + var/use_command = FALSE // If true, broadcasts will be large and BOLD. + var/command = FALSE // If true, use_command can be toggled at will. + + // Encryption key handling + var/obj/item/encryptionkey/keyslot + var/translate_binary = FALSE // If true, can hear the special binary channel. + var/independent = FALSE // If true, can say/hear on the special CentCom channel. + var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel. + var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h). + var/list/secure_radio_connections + + var/const/FREQ_LISTENING = 1 + //FREQ_BROADCASTING = 2 + +/obj/item/radio/suicide_act(mob/living/user) + user.visible_message("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/radio/proc/set_frequency(new_frequency) + SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args) + remove_radio(src, frequency) + frequency = add_radio(src, new_frequency) + +/obj/item/radio/proc/recalculateChannels() + channels = list() + translate_binary = FALSE + syndie = FALSE + independent = FALSE + + if(keyslot) + for(var/ch_name in keyslot.channels) + if(!(ch_name in channels)) + channels[ch_name] = keyslot.channels[ch_name] + + if(keyslot.translate_binary) + translate_binary = TRUE + if(keyslot.syndie) + syndie = TRUE + if(keyslot.independent) + independent = TRUE + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/proc/make_syndie() // Turns normal radios into Syndicate radios! + qdel(keyslot) + keyslot = new /obj/item/encryptionkey/syndicate + syndie = 1 + recalculateChannels() + +/obj/item/radio/Destroy() + remove_radio_all(src) //Just to be sure + QDEL_NULL(wires) + QDEL_NULL(keyslot) + return ..() + +/obj/item/radio/Initialize() + wires = new /datum/wires/radio(src) + if(prison_radio) + wires.cut(WIRE_TX) // OH GOD WHY + secure_radio_connections = new + . = ..() + frequency = sanitize_frequency(frequency, freerange) + set_frequency(frequency) + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) + +/obj/item/radio/interact(mob/user) + if(unscrewed && !isAI(user)) + wires.interact(user) + add_fingerprint(user) + else + ..() + +/obj/item/radio/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state) + . = ..() + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "radio", name, 370, 220 + channels.len * 22, master_ui, state) + ui.open() + +/obj/item/radio/ui_data(mob/user) + var/list/data = list() + + data["broadcasting"] = broadcasting + data["listening"] = listening + data["frequency"] = frequency + data["minFrequency"] = freerange ? MIN_FREE_FREQ : MIN_FREQ + data["maxFrequency"] = freerange ? MAX_FREE_FREQ : MAX_FREQ + data["freqlock"] = freqlock + data["channels"] = list() + for(var/channel in channels) + data["channels"][channel] = channels[channel] & FREQ_LISTENING + data["command"] = command + data["useCommand"] = use_command + data["subspace"] = subspace_transmission + data["subspaceSwitchable"] = subspace_switchable + data["headset"] = istype(src, /obj/item/radio/headset) + + return data + +/obj/item/radio/ui_act(action, params, datum/tgui/ui) + if(..()) + return + switch(action) + if("frequency") + if(freqlock) + return + var/tune = params["tune"] + var/adjust = text2num(params["adjust"]) + if(tune == "input") + var/min = format_frequency(freerange ? MIN_FREE_FREQ : MIN_FREQ) + var/max = format_frequency(freerange ? MAX_FREE_FREQ : MAX_FREQ) + tune = input("Tune frequency ([min]-[max]):", name, format_frequency(frequency)) as null|num + if(!isnull(tune) && !..()) + if (tune < MIN_FREE_FREQ && tune <= MAX_FREE_FREQ / 10) + // allow typing 144.7 to get 1447 + tune *= 10 + . = TRUE + else if(adjust) + tune = frequency + adjust * 10 + . = TRUE + else if(text2num(tune) != null) + tune = tune * 10 + . = TRUE + if(.) + set_frequency(sanitize_frequency(tune, freerange)) + if("listen") + listening = !listening + . = TRUE + if("broadcast") + broadcasting = !broadcasting + . = TRUE + if("channel") + var/channel = params["channel"] + if(!(channel in channels)) + return + if(channels[channel] & FREQ_LISTENING) + channels[channel] &= ~FREQ_LISTENING + else + channels[channel] |= FREQ_LISTENING + . = TRUE + if("command") + use_command = !use_command + . = TRUE + if("subspace") + if(subspace_switchable) + subspace_transmission = !subspace_transmission + if(!subspace_transmission) + channels = list() + else + recalculateChannels() + . = TRUE + +/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language) + if(!spans) + spans = list(M.speech_span) + if(!language) + language = M.get_default_language() + INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language) + return ITALICS | REDUCE_RANGE + +/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language) + if(!on) + return // the device has to be on + if(!M || !message) + return + if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios + return + if(!M.IsVocal()) + return + + if(use_command) + spans |= SPAN_COMMAND + + /* + Roughly speaking, radios attempt to make a subspace transmission (which + is received, processed, and rebroadcast by the telecomms satellite) and + if that fails, they send a mundane radio transmission. + + Headsets cannot send/receive mundane transmissions, only subspace. + Syndicate radios can hear transmissions on all well-known frequencies. + CentCom radios can hear the CentCom frequency no matter what. + */ + + // From the channel, determine the frequency and get a reference to it. + var/freq + if(channel && channels && channels.len > 0) + if(channel == MODE_DEPARTMENT) + channel = channels[1] + freq = secure_radio_connections[channel] + if (!channels[channel]) // if the channel is turned off, don't broadcast + return + else + freq = frequency + channel = null + + // Nearby active jammers severely gibberish the message + var/turf/position = get_turf(src) + for(var/obj/item/jammer/jammer in GLOB.active_jammers) + var/turf/jammer_turf = get_turf(jammer) + if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) < jammer.range)) + message = Gibberish(message,100) + break + + // Determine the identity information which will be attached to the signal. + var/atom/movable/virtualspeaker/speaker = new(null, M, src) + + // Construct the signal + var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) + + // Independent radios, on the CentCom frequency, reach all independent radios + if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_SUPERSPACE + signal.levels = list(0) // reaches all Z-levels + signal.broadcast() + return + + // All radios make an attempt to use the subspace system first + signal.send_to_receivers() + + // If the radio is subspace-only, that's all it can do + if (subspace_transmission) + return + + // Non-subspace radios will check in a couple of seconds, and if the signal + // was never received, send a mundane broadcast (no headsets). + addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) + +/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) + var/turf/T = get_turf(src) + if (signal.data["done"] && (T.z in signal.levels)) + return + + // Okay, the signal was never processed, send a mundane broadcast. + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_RADIO + signal.levels = list(T.z) + signal.broadcast() + +/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) + return + + if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) + // radios don't pick up whispers very well + raw_message = stars(raw_message) + else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) + // try to avoid being heard double + if (loc == speaker && ismob(speaker)) + var/mob/M = speaker + var/idx = M.get_held_index_of_item(src) + // left hands are odd slots + if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) + return + + talk_into(speaker, raw_message, , spans, language=message_language) + +// Checks if this radio can receive on the given frequency. +/obj/item/radio/proc/can_receive(freq, level) + // deny checks + if (!on || !listening || wires.is_cut(WIRE_RX)) + return FALSE + if (freq == FREQ_SYNDICATE && !syndie) + return FALSE + if (freq == FREQ_CENTCOM) + return independent // hard-ignores the z-level check + if (!(0 in level)) + var/turf/position = get_turf(src) + if(!position || !(position.z in level)) + return FALSE + + // allow checks: are we listening on that frequency? + if (freq == frequency) + return TRUE + for(var/ch_name in channels) + if(channels[ch_name] & FREQ_LISTENING) + //the GLOB.radiochannels list is located in communications.dm + if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) + return TRUE + return FALSE + + +/obj/item/radio/examine(mob/user) + . = ..() + if (unscrewed) + . += "It can be attached and modified." + else + . += "It cannot be modified or attached." + +/obj/item/radio/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(istype(W, /obj/item/screwdriver)) + unscrewed = !unscrewed + if(unscrewed) + to_chat(user, "The radio can now be attached and modified!") + else + to_chat(user, "The radio can no longer be modified or attached!") + else + return ..() + +/obj/item/radio/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + emped++ //There's been an EMP; better count it + var/curremp = emped //Remember which EMP this was + if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice + to_chat(loc, "\The [src] overloads.") + broadcasting = FALSE + listening = FALSE + for (var/ch_name in channels) + channels[ch_name] = 0 + on = FALSE + spawn(200) + if(emped == curremp) //Don't fix it if it's been EMP'd again + emped = 0 + if (!istype(src, /obj/item/radio/intercom)) // intercoms will turn back on on their own + on = TRUE + +/////////////////////////////// +//////////Borg Radios////////// +/////////////////////////////// +//Giving borgs their own radio to have some more room to work with -Sieve + +/obj/item/radio/borg + name = "cyborg radio" + subspace_switchable = TRUE + dog_fashion = null + +/obj/item/radio/borg/Initialize(mapload) + . = ..() + +/obj/item/radio/borg/syndicate + syndie = 1 + keyslot = new /obj/item/encryptionkey/syndicate + +/obj/item/radio/borg/syndicate/Initialize() + . = ..() + set_frequency(FREQ_SYNDICATE) + +/obj/item/radio/borg/attackby(obj/item/W, mob/user, params) + + if(istype(W, /obj/item/screwdriver)) + if(keyslot) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + + if(keyslot) + var/turf/T = get_turf(user) + if(T) + keyslot.forceMove(T) + keyslot = null + + recalculateChannels() + to_chat(user, "You pop out the encryption key in the radio.") + + else + to_chat(user, "This radio doesn't have any encryption keys!") + + else if(istype(W, /obj/item/encryptionkey/)) + if(keyslot) + to_chat(user, "The radio can't hold another key!") + return + + if(!keyslot) + if(!user.transferItemToLoc(W, src)) + return + keyslot = W + + recalculateChannels() + + +/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. + listening = 0 // And it's nice to have a subtype too for future features. + dog_fashion = /datum/dog_fashion/back + +/obj/item/radio/internal + var/obj/item/implant/radio/implant + +/obj/item/radio/internal/Initialize(mapload, obj/item/implant/radio/_implant) + . = ..() + implant = _implant + +/obj/item/radio/internal/Destroy() + if(implant?.imp_in) + qdel(implant) + else + return ..() diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index 132888a7..094d9b5f 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -1,290 +1,290 @@ -/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" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = HEAR_1 - slot_flags = ITEM_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/starting_tape_type = /obj/item/tape/random - var/open_panel = 0 - var/canprint = 1 - - -/obj/item/taperecorder/Initialize(mapload) - . = ..() - if(starting_tape_type) - mytape = new starting_tape_type(src) - update_icon() - - -/obj/item/taperecorder/examine(mob/user) - . = ..() - . += "The wire panel is [open_panel ? "opened" : "closed"]." - -/obj/item/taperecorder/attackby(obj/item/I, mob/user, params) - if(!mytape && istype(I, /obj/item/tape)) - if(!user.transferItemToLoc(I,src)) - return - 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(exposed_temperature, exposed_volume) - mytape.ruin() //Fires destroy the tape - ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/taperecorder/attack_hand(mob/user) - if(loc == user) - if(mytape) - if(!user.is_holding(src)) - return ..() - eject(user) - else - return ..() - -/obj/item/taperecorder/proc/can_use(mob/user) - if(user && ismob(user)) - if(!user.incapacitated()) - return TRUE - return FALSE - - -/obj/item/taperecorder/verb/ejectverb() - set name = "Eject Tape" - set category = "Object" - - if(!can_use(usr)) - 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(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) - . = ..() - if(mytape && recording) - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [message]" - -/obj/item/taperecorder/verb/record() - set name = "Start Recording" - set category = "Object" - - if(!can_use(usr)) - 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 - while(recording && used < max) - 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(!can_use(usr)) - 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 - var/turf/T = get_turf(src) - T.visible_message("Tape Recorder: Playback stopped.") - update_icon() - - -/obj/item/taperecorder/verb/play() - set name = "Play Tape" - set category = "Object" - - if(!can_use(usr)) - 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 - say(mytape.storedinfo[i]) - if(mytape.storedinfo.len < i + 1) - playsleepseconds = 1 - sleep(10) - say("End of recording.") - else - playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] - if(playsleepseconds > 14) - sleep(10) - 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(!can_use(usr)) - 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.") - 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 - starting_tape_type = null - - -/obj/item/tape - name = "tape" - desc = "A magnetic tape that can hold up to ten minutes of content." - icon_state = "tape_white" - icon = 'icons/obj/device.dmi' - item_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - 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(exposed_temperature, exposed_volume) - ruin() - ..() - -/obj/item/tape/attack_self(mob/user) - if(!ruined) - to_chat(user, "You pull out all the tape!") - ruin() - - -/obj/item/tape/proc/ruin() - //Lets not add infinite amounts of overlays when our fireact is called - //repeatedly - if(!ruined) - add_overlay("ribbonoverlay") - ruined = 1 - - -/obj/item/tape/proc/fix() - cut_overlay("ribbonoverlay") - ruined = 0 - - -/obj/item/tape/attackby(obj/item/I, mob/user, params) - if(ruined && istype(I, /obj/item/screwdriver) || istype(I, /obj/item/pen)) - to_chat(user, "You start winding the tape back in...") - if(I.use_tool(src, user, 120)) - to_chat(user, "You wound the tape back in.") - fix() - -//Random colour tapes -/obj/item/tape/random - icon_state = "random_tape" - -/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" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = HEAR_1 + slot_flags = ITEM_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/starting_tape_type = /obj/item/tape/random + var/open_panel = 0 + var/canprint = 1 + + +/obj/item/taperecorder/Initialize(mapload) + . = ..() + if(starting_tape_type) + mytape = new starting_tape_type(src) + update_icon() + + +/obj/item/taperecorder/examine(mob/user) + . = ..() + . += "The wire panel is [open_panel ? "opened" : "closed"]." + +/obj/item/taperecorder/attackby(obj/item/I, mob/user, params) + if(!mytape && istype(I, /obj/item/tape)) + if(!user.transferItemToLoc(I,src)) + return + 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(exposed_temperature, exposed_volume) + mytape.ruin() //Fires destroy the tape + ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/taperecorder/attack_hand(mob/user) + if(loc == user) + if(mytape) + if(!user.is_holding(src)) + return ..() + eject(user) + else + return ..() + +/obj/item/taperecorder/proc/can_use(mob/user) + if(user && ismob(user)) + if(!user.incapacitated()) + return TRUE + return FALSE + + +/obj/item/taperecorder/verb/ejectverb() + set name = "Eject Tape" + set category = "Object" + + if(!can_use(usr)) + 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(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source) + . = ..() + if(mytape && recording) + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [message]" + +/obj/item/taperecorder/verb/record() + set name = "Start Recording" + set category = "Object" + + if(!can_use(usr)) + 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 + while(recording && used < max) + 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(!can_use(usr)) + 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 + var/turf/T = get_turf(src) + T.visible_message("Tape Recorder: Playback stopped.") + update_icon() + + +/obj/item/taperecorder/verb/play() + set name = "Play Tape" + set category = "Object" + + if(!can_use(usr)) + 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 + say(mytape.storedinfo[i]) + if(mytape.storedinfo.len < i + 1) + playsleepseconds = 1 + sleep(10) + say("End of recording.") + else + playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] + if(playsleepseconds > 14) + sleep(10) + 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(!can_use(usr)) + 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.") + 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 + starting_tape_type = null + + +/obj/item/tape + name = "tape" + desc = "A magnetic tape that can hold up to ten minutes of content." + icon_state = "tape_white" + icon = 'icons/obj/device.dmi' + item_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + 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(exposed_temperature, exposed_volume) + ruin() + ..() + +/obj/item/tape/attack_self(mob/user) + if(!ruined) + to_chat(user, "You pull out all the tape!") + ruin() + + +/obj/item/tape/proc/ruin() + //Lets not add infinite amounts of overlays when our fireact is called + //repeatedly + if(!ruined) + add_overlay("ribbonoverlay") + ruined = 1 + + +/obj/item/tape/proc/fix() + cut_overlay("ribbonoverlay") + ruined = 0 + + +/obj/item/tape/attackby(obj/item/I, mob/user, params) + if(ruined && istype(I, /obj/item/screwdriver) || istype(I, /obj/item/pen)) + to_chat(user, "You start winding the tape back in...") + if(I.use_tool(src, user, 120)) + to_chat(user, "You wound the tape back in.") + fix() + +//Random colour tapes +/obj/item/tape/random + icon_state = "random_tape" + +/obj/item/tape/random/New() + icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" + ..() diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm index bdaa7160..3e7cec1e 100644 --- a/code/game/objects/items/eightball.dm +++ b/code/game/objects/items/eightball.dm @@ -122,7 +122,7 @@ interact(user) return ..() -/obj/item/toy/eightball/haunted/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) +/obj/item/toy/eightball/haunted/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source) . = ..() last_message = raw_message diff --git a/code/game/say.dm b/code/game/say.dm index 8f7c9704..d5166b64 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -28,19 +28,21 @@ GLOBAL_LIST_INIT(freqtospan, list( language = get_default_language() send_speech(message, 7, src, , spans, message_language=language) -/atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) +/atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args) /atom/movable/proc/can_speak() return 1 -/atom/movable/proc/send_speech(message, range = 7, obj/source = src, bubble_type, list/spans, datum/language/message_language = null, message_mode) - var/rendered = compose_message(src, message_language, message, , spans, message_mode) +/atom/movable/proc/send_speech(message, range = 7, atom/movable/source = src, bubble_type, list/spans, datum/language/message_language = null, message_mode) + var/rendered = compose_message(src, message_language, message, , spans, message_mode, source) for(var/_AM in get_hearers_in_view(range, source)) var/atom/movable/AM = _AM - AM.Hear(rendered, src, message_language, message, , spans, message_mode) + AM.Hear(rendered, src, message_language, message, , spans, message_mode, source) -/atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE) +/atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE, atom/movable/source) + if(!source) + source = speaker //This proc uses text() because it is faster than appending strings. Thanks BYOND. //Basic span var/spanpart1 = "" diff --git a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm index 47faea6d..86099d8c 100644 --- a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm +++ b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm @@ -98,7 +98,7 @@ else to_chat(M, message) -/mob/camera/eminence/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) +/mob/camera/eminence/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) . = ..() if(is_reebe(z) || is_servant_of_ratvar(speaker) || GLOB.ratvar_approaches || GLOB.ratvar_awakens) //Away from Reebe, the Eminence can't hear anything to_chat(src, message) diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm index adb448d2..86a7dd01 100644 --- a/code/modules/antagonists/disease/disease_mob.dm +++ b/code/modules/antagonists/disease/disease_mob.dm @@ -118,7 +118,7 @@ the new instance inside the host to be updated to the template's stats. follow_next(Dir & NORTHWEST) last_move_tick = world.time -/mob/camera/disease/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) +/mob/camera/disease/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) . = ..() var/atom/movable/to_follow = speaker if(radio_freq) @@ -130,7 +130,7 @@ the new instance inside the host to be updated to the template's stats. else link = "" // Recompose the message, because it's scrambled by default - message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode) + message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) to_chat(src, "[link] [message]") diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index b54b18eb..f72f7269 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -1,101 +1,101 @@ -#define INCLUSIVE_MODE 1 -#define EXCLUSIVE_MODE 2 -#define RECOGNIZER_MODE 3 -#define VOICE_SENSOR_MODE 4 - -/obj/item/assembly/voice - name = "voice analyzer" - desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." - icon_state = "voice" - materials = list(MAT_METAL=500, MAT_GLASS=50) - flags_1 = HEAR_1 - attachable = TRUE - verb_say = "beeps" - verb_ask = "beeps" - verb_exclaim = "beeps" - var/listening = FALSE - var/recorded = "" //the activation message - var/mode = 1 - var/static/list/modes = list("inclusive", - "exclusive", - "recognizer", - "voice sensor") - -/obj/item/assembly/voice/examine(mob/user) - . = ..() - . += "Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode." - -/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker == src) - return - - if(listening && !radio_freq) - record_speech(speaker, raw_message, message_language) - else - if(check_activation(speaker, raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) - switch(mode) - if(INCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(EXCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(RECOGNIZER_MODE) - recorded = speaker.GetVoice() - listening = FALSE - say("Your voice pattern is saved.", message_language) - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) - . = FALSE - switch(mode) - if(INCLUSIVE_MODE) - if(findtext(raw_message, recorded)) - . = TRUE - if(EXCLUSIVE_MODE) - if(raw_message == recorded) - . = TRUE - if(RECOGNIZER_MODE) - if(speaker.GetVoice() == recorded) - . = TRUE - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - . = TRUE - -/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) - mode %= modes.len - mode++ - to_chat(user, "You set [src] into [modes[mode]] mode.") - listening = FALSE - recorded = "" - return TRUE - -/obj/item/assembly/voice/activate() - if(!secured || holder) - return FALSE - listening = !listening - say("[listening ? "Now" : "No longer"] recording input.") - return TRUE - -/obj/item/assembly/voice/attack_self(mob/user) - if(!user) - return FALSE - activate() - return TRUE - -/obj/item/assembly/voice/toggle_secure() - . = ..() - listening = FALSE - -#undef INCLUSIVE_MODE -#undef EXCLUSIVE_MODE -#undef RECOGNIZER_MODE -#undef VOICE_SENSOR_MODE +#define INCLUSIVE_MODE 1 +#define EXCLUSIVE_MODE 2 +#define RECOGNIZER_MODE 3 +#define VOICE_SENSOR_MODE 4 + +/obj/item/assembly/voice + name = "voice analyzer" + desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." + icon_state = "voice" + materials = list(MAT_METAL=500, MAT_GLASS=50) + flags_1 = HEAR_1 + attachable = TRUE + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + var/listening = FALSE + var/recorded = "" //the activation message + var/mode = 1 + var/static/list/modes = list("inclusive", + "exclusive", + "recognizer", + "voice sensor") + +/obj/item/assembly/voice/examine(mob/user) + . = ..() + . += "Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode." + +/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(speaker == src) + return + + if(listening && !radio_freq) + record_speech(speaker, raw_message, message_language) + else + if(check_activation(speaker, raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) + switch(mode) + if(INCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(EXCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(RECOGNIZER_MODE) + recorded = speaker.GetVoice() + listening = FALSE + say("Your voice pattern is saved.", message_language) + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) + . = FALSE + switch(mode) + if(INCLUSIVE_MODE) + if(findtext(raw_message, recorded)) + . = TRUE + if(EXCLUSIVE_MODE) + if(raw_message == recorded) + . = TRUE + if(RECOGNIZER_MODE) + if(speaker.GetVoice() == recorded) + . = TRUE + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + . = TRUE + +/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) + mode %= modes.len + mode++ + to_chat(user, "You set [src] into [modes[mode]] mode.") + listening = FALSE + recorded = "" + return TRUE + +/obj/item/assembly/voice/activate() + if(!secured || holder) + return FALSE + listening = !listening + say("[listening ? "Now" : "No longer"] recording input.") + return TRUE + +/obj/item/assembly/voice/attack_self(mob/user) + if(!user) + return FALSE + activate() + return TRUE + +/obj/item/assembly/voice/toggle_secure() + . = ..() + listening = FALSE + +#undef INCLUSIVE_MODE +#undef EXCLUSIVE_MODE +#undef RECOGNIZER_MODE +#undef VOICE_SENSOR_MODE diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm index b7feedd3..46f8017b 100644 --- a/code/modules/integrated_electronics/subtypes/input.dm +++ b/code/modules/integrated_electronics/subtypes/input.dm @@ -859,7 +859,7 @@ spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH power_draw_per_use = 5 -/obj/item/integrated_circuit/input/microphone/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) +/obj/item/integrated_circuit/input/microphone/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source) . = ..() var/translated = FALSE if(speaker && message) diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm index 70fbba8d..0d626d67 100644 --- a/code/modules/mob/dead/observer/say.dm +++ b/code/modules/mob/dead/observer/say.dm @@ -1,43 +1,43 @@ -/mob/dead/observer/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) - if (!message) - return - - var/message_mode = get_message_mode(message) - if(client && (message_mode == MODE_ADMIN || message_mode == MODE_DEADMIN)) - message = copytext(message, 3) - if(findtext(message, " ", 1, 2)) - message = copytext(message, 2) - - if(message_mode == MODE_ADMIN) - client.cmd_admin_say(message) - else if(message_mode == MODE_DEADMIN) - client.dsay(message) - return - - src.log_talk(message, LOG_SAY, tag="ghost") - - if(check_emote(message)) - return - - . = say_dead(message) - -/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - var/atom/movable/to_follow = speaker - if(radio_freq) - var/atom/movable/virtualspeaker/V = speaker - - if(isAI(V.source)) - var/mob/living/silicon/ai/S = V.source - to_follow = S.eyeobj - else - to_follow = V.source - var/link = FOLLOW_LINK(src, to_follow) - // Create map text prior to modifying message for goonchat - if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) - create_chat_message(speaker, message_language, raw_message, spans, message_mode) - // Recompose the message, because it's scrambled by default - message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode) - to_chat(src, "[link] [message]") - +/mob/dead/observer/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) + if (!message) + return + + var/message_mode = get_message_mode(message) + if(client && (message_mode == MODE_ADMIN || message_mode == MODE_DEADMIN)) + message = copytext(message, 3) + if(findtext(message, " ", 1, 2)) + message = copytext(message, 2) + + if(message_mode == MODE_ADMIN) + client.cmd_admin_say(message) + else if(message_mode == MODE_DEADMIN) + client.dsay(message) + return + + src.log_talk(message, LOG_SAY, tag="ghost") + + if(check_emote(message)) + return + + . = say_dead(message) + +/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + var/atom/movable/to_follow = speaker + if(radio_freq) + var/atom/movable/virtualspeaker/V = speaker + + if(isAI(V.source)) + var/mob/living/silicon/ai/S = V.source + to_follow = S.eyeobj + else + to_follow = V.source + var/link = FOLLOW_LINK(src, to_follow) + // Create map text prior to modifying message for goonchat + if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) + // Recompose the message, because it's scrambled by default + message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) + to_chat(src, "[link] [message]") + diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 8d2c75d7..162ca300 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -221,11 +221,11 @@ GLOBAL_LIST_INIT(department_radio_keys, list( if(succumbed) succumb() - to_chat(src, compose_message(src, language, message, , spans, message_mode)) + to_chat(src, compose_message(src, language, message, null, spans, message_mode)) return 1 -/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) +/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) . = ..() if(!client) return @@ -245,8 +245,8 @@ GLOBAL_LIST_INIT(department_radio_keys, list( playsound_local(src,'sound/voice/radio.ogg', 30, 1) // Recompose message for AI hrefs, language incomprehension. - message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode) - message = hear_intercept(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) + message = hear_intercept(message, speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) show_message(message, MSG_AUDIBLE, deaf_message, deaf_type) return message @@ -262,8 +262,8 @@ GLOBAL_LIST_INIT(department_radio_keys, list( var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source) var/list/the_dead = list() var/list/yellareas //CIT CHANGE - adds the ability for yelling to penetrate walls and echo throughout areas - if(say_test(message) == "2") //CIT CHANGE - ditto - yellareas = get_areas_in_range(message_range*0.5,src) //CIT CHANGE - ditto + if(!eavesdrop_range && say_test(message) == "2") //CIT CHANGE - ditto + yellareas = get_areas_in_range(message_range*0.5, source) //CIT CHANGE - ditto for(var/_M in GLOB.player_list) var/mob/M = _M if(M.stat != DEAD) //not dead, not important @@ -274,7 +274,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list( continue if(!M.client || !client) //client is so that ghosts don't have to listen to mice continue - if(get_dist(M, src) > 7 || M.z != z) //they're out of range of normal hearing + if(get_dist(M, source) > 7 || M.z != z) //they're out of range of normal hearing if(eavesdropping_modes[message_mode] && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off continue if(!(M.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off @@ -286,15 +286,15 @@ GLOBAL_LIST_INIT(department_radio_keys, list( var/eavesrendered if(eavesdrop_range) eavesdropping = stars(message) - eavesrendered = compose_message(src, message_language, eavesdropping, , spans, message_mode) + eavesrendered = compose_message(src, message_language, eavesdropping, null, spans, message_mode, FALSE, source) - var/rendered = compose_message(src, message_language, message, , spans, message_mode) + var/rendered = compose_message(src, message_language, message, null, spans, message_mode, FALSE, source) for(var/_AM in listening) var/atom/movable/AM = _AM if(eavesdrop_range && get_dist(source, AM) > message_range && !(the_dead[AM])) - AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mode) + AM.Hear(eavesrendered, src, message_language, eavesdropping, null, spans, message_mode, source) else - AM.Hear(rendered, src, message_language, message, , spans, message_mode) + AM.Hear(rendered, src, message_language, message, null, spans, message_mode, source) SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message) //speech bubble diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index d8843376..e5484a3c 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -1,197 +1,197 @@ -// 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" - - invisibility = INVISIBILITY_MAXIMUM - hud_possible = list(ANTAG_HUD, AI_DETECT_HUD = HUD_LIST_LIST) - var/list/visibleCameraChunks = list() - var/mob/living/silicon/ai/ai = null - var/relay_speech = FALSE - var/use_static = USE_STATIC_OPAQUE - var/static_visibility_range = 16 - var/ai_detector_visible = TRUE - var/ai_detector_color = COLOR_RED - -/mob/camera/aiEye/Initialize() - . = ..() - GLOB.aiEyes += src - update_ai_detect_hud() - setLoc(loc, TRUE) - -/mob/camera/aiEye/proc/update_ai_detect_hud() - var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] - var/list/old_images = hud_list[AI_DETECT_HUD] - if(!ai_detector_visible) - hud.remove_from_hud(src) - QDEL_LIST(old_images) - return - - if(!hud.hudusers.len) - //no one is watching, do not bother updating anything - return - hud.remove_from_hud(src) - - var/static/list/vis_contents_objects = list() - var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_objects[ai_detector_color] - if(!hud_obj) - hud_obj = new /obj/effect/overlay/ai_detect_hud() - hud_obj.color = ai_detector_color - vis_contents_objects[ai_detector_color] = hud_obj - - var/list/new_images = list() - var/list/turfs = get_visible_turfs() - for(var/T in turfs) - var/image/I = (old_images.len > new_images.len) ? old_images[new_images.len + 1] : image(null, T) - I.loc = T - I.vis_contents += hud_obj - new_images += I - for(var/i in (new_images.len + 1) to old_images.len) - qdel(old_images[i]) - hud_list[AI_DETECT_HUD] = new_images - hud.add_to_hud(src) - -/mob/camera/aiEye/proc/get_visible_turfs() - if(!isturf(loc)) - return list() - var/client/C = GetViewerClient() - var/view = C ? getviewsize(C.view) : getviewsize(world.view) - var/turf/lowerleft = locate(max(1, x - (view[1] - 1)/2), max(1, y - (view[2] - 1)/2), z) - var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z) - return block(lowerleft, upperright) - -// Use this when setting the aiEye's location. -// It will also stream the chunk that the new loc is in. - -/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE) - if(ai) - if(!isturf(ai.loc)) - return - T = get_turf(T) - if(!force_update && (T == get_turf(src)) ) - return //we are already here! - if (T) - forceMove(T) - else - moveToNullspace() - if(use_static != USE_STATIC_NONE) - ai.camera_visibility(src) - if(ai.client && !ai.multicam_on) - ai.client.eye = src - update_ai_detect_hud() - update_parallax_contents() - //Holopad - if(istype(ai.current, /obj/machinery/holopad)) - var/obj/machinery/holopad/H = ai.current - H.move_hologram(ai, T) - if(ai.camera_light_on) - ai.light_cameras() - if(ai.master_multicam) - ai.master_multicam.refresh_view() - -/mob/camera/aiEye/Move() - return 0 - -/mob/camera/aiEye/proc/GetViewerClient() - if(ai) - return ai.client - return null - -/mob/camera/aiEye/Destroy() - if(ai) - ai.all_eyes -= src - ai = null - for(var/V in visibleCameraChunks) - var/datum/camerachunk/c = V - c.remove(src) - GLOB.aiEyes -= src - if(ai_detector_visible) - var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] - hud.remove_from_hud(src) - var/list/L = hud_list[AI_DETECT_HUD] - QDEL_LIST(L) - return ..() - -/atom/proc/move_camera_by_click() - if(isAI(usr)) - 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) - -// 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, 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 - -// Return to the Core. -/mob/living/silicon/ai/proc/view_core() - if(istype(current,/obj/machinery/holopad)) - var/obj/machinery/holopad/H = current - H.clear_holo(src) - else - current = null - cameraFollow = null - unset_machine() - - if(!eyeobj || !eyeobj.loc || QDELETED(eyeobj)) - 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/verb/toggle_acceleration() - set category = "AI Commands" - set name = "Toggle Camera Acceleration" - - if(incapacitated()) - return - acceleration = !acceleration - to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") - -/mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker)) - ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - -/obj/effect/overlay/ai_detect_hud - name = "" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - icon = 'icons/effects/alphacolors.dmi' - icon_state = "" - alpha = 100 - layer = ABOVE_ALL_MOB_LAYER - plane = GAME_PLANE +// 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" + + invisibility = INVISIBILITY_MAXIMUM + hud_possible = list(ANTAG_HUD, AI_DETECT_HUD = HUD_LIST_LIST) + var/list/visibleCameraChunks = list() + var/mob/living/silicon/ai/ai = null + var/relay_speech = FALSE + var/use_static = USE_STATIC_OPAQUE + var/static_visibility_range = 16 + var/ai_detector_visible = TRUE + var/ai_detector_color = COLOR_RED + +/mob/camera/aiEye/Initialize() + . = ..() + GLOB.aiEyes += src + update_ai_detect_hud() + setLoc(loc, TRUE) + +/mob/camera/aiEye/proc/update_ai_detect_hud() + var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] + var/list/old_images = hud_list[AI_DETECT_HUD] + if(!ai_detector_visible) + hud.remove_from_hud(src) + QDEL_LIST(old_images) + return + + if(!hud.hudusers.len) + //no one is watching, do not bother updating anything + return + hud.remove_from_hud(src) + + var/static/list/vis_contents_objects = list() + var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_objects[ai_detector_color] + if(!hud_obj) + hud_obj = new /obj/effect/overlay/ai_detect_hud() + hud_obj.color = ai_detector_color + vis_contents_objects[ai_detector_color] = hud_obj + + var/list/new_images = list() + var/list/turfs = get_visible_turfs() + for(var/T in turfs) + var/image/I = (old_images.len > new_images.len) ? old_images[new_images.len + 1] : image(null, T) + I.loc = T + I.vis_contents += hud_obj + new_images += I + for(var/i in (new_images.len + 1) to old_images.len) + qdel(old_images[i]) + hud_list[AI_DETECT_HUD] = new_images + hud.add_to_hud(src) + +/mob/camera/aiEye/proc/get_visible_turfs() + if(!isturf(loc)) + return list() + var/client/C = GetViewerClient() + var/view = C ? getviewsize(C.view) : getviewsize(world.view) + var/turf/lowerleft = locate(max(1, x - (view[1] - 1)/2), max(1, y - (view[2] - 1)/2), z) + var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z) + return block(lowerleft, upperright) + +// Use this when setting the aiEye's location. +// It will also stream the chunk that the new loc is in. + +/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE) + if(ai) + if(!isturf(ai.loc)) + return + T = get_turf(T) + if(!force_update && (T == get_turf(src)) ) + return //we are already here! + if (T) + forceMove(T) + else + moveToNullspace() + if(use_static != USE_STATIC_NONE) + ai.camera_visibility(src) + if(ai.client && !ai.multicam_on) + ai.client.eye = src + update_ai_detect_hud() + update_parallax_contents() + //Holopad + if(istype(ai.current, /obj/machinery/holopad)) + var/obj/machinery/holopad/H = ai.current + H.move_hologram(ai, T) + if(ai.camera_light_on) + ai.light_cameras() + if(ai.master_multicam) + ai.master_multicam.refresh_view() + +/mob/camera/aiEye/Move() + return 0 + +/mob/camera/aiEye/proc/GetViewerClient() + if(ai) + return ai.client + return null + +/mob/camera/aiEye/Destroy() + if(ai) + ai.all_eyes -= src + ai = null + for(var/V in visibleCameraChunks) + var/datum/camerachunk/c = V + c.remove(src) + GLOB.aiEyes -= src + if(ai_detector_visible) + var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] + hud.remove_from_hud(src) + var/list/L = hud_list[AI_DETECT_HUD] + QDEL_LIST(L) + return ..() + +/atom/proc/move_camera_by_click() + if(isAI(usr)) + 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) + +// 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, 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 + +// Return to the Core. +/mob/living/silicon/ai/proc/view_core() + if(istype(current,/obj/machinery/holopad)) + var/obj/machinery/holopad/H = current + H.clear_holo(src) + else + current = null + cameraFollow = null + unset_machine() + + if(!eyeobj || !eyeobj.loc || QDELETED(eyeobj)) + 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/verb/toggle_acceleration() + set category = "AI Commands" + set name = "Toggle Camera Acceleration" + + if(incapacitated()) + return + acceleration = !acceleration + to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") + +/mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker)) + ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + +/obj/effect/overlay/ai_detect_hud + name = "" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + icon = 'icons/effects/alphacolors.dmi' + icon_state = "" + alpha = 100 + layer = ABOVE_ALL_MOB_LAYER + plane = GAME_PLANE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 60fe4d27..0308443d 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -378,7 +378,7 @@ Difficulty: Very Hard . += observer_desc . += "It is activated by [activation_method]." -/obj/machinery/anomalous_crystal/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) +/obj/machinery/anomalous_crystal/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source) ..() if(isliving(speaker)) ActivationReaction(speaker, ACTIVATE_SPEECH) diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 134dc9f0..d303f6a8 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -1,1019 +1,1019 @@ -/* Parrots! - * Contains - * Defines - * Inventory (headset stuff) - * Attack responces - * AI - * Procs / Verbs (usable by players) - * Sub-types - * Hear & say (the things we do for gimmicks) - */ - -/* - * Defines - */ - -//Only a maximum of one action and one intent should be active at any given time. -//Actions -#define PARROT_PERCH (1<<0) //Sitting/sleeping, not moving -#define PARROT_SWOOP (1<<1) //Moving towards or away from a target -#define PARROT_WANDER (1<<2) //Moving without a specific target in mind - -//Intents -#define PARROT_STEAL (1<<3) //Flying towards a target to steal it/from it -#define PARROT_ATTACK (1<<4) //Flying towards a target to attack it -#define PARROT_RETURN (1<<5) //Flying towards its perch -#define PARROT_FLEE (1<<6) //Flying away from its attacker - - -/mob/living/simple_animal/parrot - name = "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" - var/icon_sit = "parrot_sit" - density = FALSE - health = 80 - maxHealth = 80 - pass_flags = PASSTABLE | PASSMOB - - 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/ = 1) - melee_damage_upper = 10 - melee_damage_lower = 5 - - response_help = "pets" - response_disarm = "gently moves aside" - response_harm = "swats" - stop_automated_movement = 1 - a_intent = INTENT_HARM //parrots now start "aggressive" since only player parrots will nuzzle. - attacktext = "chomps" - friendly = "grooms" - mob_size = MOB_SIZE_SMALL - movement_type = FLYING - gold_core_spawnable = FRIENDLY_SPAWN - - var/parrot_damage_upper = 10 - 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 life() 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(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_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_lastmove = null //Updates/Stores position of the parrot while it's moving - var/parrot_stuck = 0 //If parrot_lastmove hasnt changed, this will increment until it reaches parrot_stuck_threshold - var/parrot_stuck_threshold = 10 //if this == parrot_stuck, it'll force the parrot back to wandering - - var/list/speech_buffer = list() - var/speech_shuffle_rate = 20 - 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 perch 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/frame/computer, /obj/structure/displaycase, \ - /obj/structure/filingcabinet, /obj/machinery/teleport, \ - /obj/machinery/computer, /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/suit_storage_unit) - - //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. - var/obj/item/held_item = null - - -/mob/living/simple_animal/parrot/Initialize() - . = ..() - 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) - - 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/proc/toggle_mode, - /mob/living/simple_animal/parrot/proc/perch_mob_player) - - -/mob/living/simple_animal/parrot/examine(mob/user) - . = ..() - if(stat) - . += pick("This parrot is no more.", "This is a late parrot.", "This is an ex-parrot.") - -/mob/living/simple_animal/parrot/death(gibbed) - if(held_item) - held_item.forceMove(drop_location()) - held_item = null - walk(src,0) - - if(buckled) - buckled.unbuckle_mob(src,force=1) - buckled = null - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - - ..(gibbed) - -/mob/living/simple_animal/parrot/Stat() - ..() - if(statpanel("Status")) - stat("Held Item", held_item) - stat("Mode",a_intent) - -/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker != src && prob(50)) //Dont imitate ourselves - if(!radio_freq || prob(10)) - if(speech_buffer.len >= 500) - speech_buffer -= pick(speech_buffer) - speech_buffer |= html_decode(raw_message) - if(speaker == src && !client) //If a parrot squawks in the woods and no one is around to hear it, does it make a sound? This code says yes! - return message - -/mob/living/simple_animal/parrot/radio(message, message_mode, list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be. - . = ..() - if(. != 0) - return . - - switch(message_mode) - if(MODE_HEADSET) - if (ears) - ears.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_DEPARTMENT) - if (ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - if(message_mode in GLOB.radiochannels) - if(ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - return 0 - -/* - * Inventory - */ -/mob/living/simple_animal/parrot/show_inv(mob/user) - user.set_machine(src) - - var/dat = "
Inventory of [name]

" - dat += "
Headset: [ears]" : "add_inv=ears'>Nothing"]" - - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "window=mob[REF(src)]") - - -/mob/living/simple_animal/parrot/Topic(href, href_list) - if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=mob[REF(src)]") - usr.unset_machine() - return - - //Removing from inventory - if(href_list["remove_inv"]) - var/remove_from = href_list["remove_inv"] - switch(remove_from) - if("ears") - if(!ears) - to_chat(usr, "There is nothing to remove from its [remove_from]!") - return - if(!stat) - say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - ears.forceMove(drop_location()) - ears = null - for(var/possible_phrase in speak) - if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys) - possible_phrase = copytext(possible_phrase,3) - - //Adding things to inventory - else if(href_list["add_inv"]) - var/add_to = href_list["add_inv"] - if(!usr.get_active_held_item()) - 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_held_item() - 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 - - if(!usr.transferItemToLoc(headset_to_add, src)) - return - ears = headset_to_add - to_chat(usr, "You fit the headset onto [src].") - - clearlist(available_channels) - for(var/ch in headset_to_add.channels) - switch(ch) - if(RADIO_CHANNEL_ENGINEERING) - available_channels.Add(RADIO_TOKEN_ENGINEERING) - if(RADIO_CHANNEL_COMMAND) - available_channels.Add(RADIO_TOKEN_COMMAND) - if(RADIO_CHANNEL_SECURITY) - available_channels.Add(RADIO_TOKEN_SECURITY) - if(RADIO_CHANNEL_SCIENCE) - available_channels.Add(RADIO_TOKEN_SCIENCE) - if(RADIO_CHANNEL_MEDICAL) - available_channels.Add(RADIO_TOKEN_MEDICAL) - if(RADIO_CHANNEL_SUPPLY) - available_channels.Add(RADIO_TOKEN_SUPPLY) - if(RADIO_CHANNEL_SERVICE) - available_channels.Add(RADIO_TOKEN_SERVICE) - - if(headset_to_add.translate_binary) - available_channels.Add(MODE_TOKEN_BINARY) - else - return ..() - - -/* - * Attack responces - */ -//Humans, monkeys, aliens -/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M) - ..() - if(client) - return - if(!stat && M.a_intent == INTENT_HARM) - - icon_state = icon_living //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(health > 30) //Let's get in there and squawk it up! - parrot_state |= PARROT_ATTACK - else - parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! - drop_held_item(0) - if(stat != DEAD && M.a_intent == INTENT_HELP) - handle_automated_speech(1) //assured speak/emote - return - -/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/monkey/M) - return attack_hand(M) - -/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/alien/M) - return attack_hand(M) - -//Simple animals -/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/M) - . = ..() //goodbye immortal parrots - - if(client) - return - - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - if(M.melee_damage_upper > 0 && !stat) - parrot_interest = M - parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless - icon_state = icon_living - -//Mobs with objects -/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/living/user, params) - if(!stat && !client && !istype(O, /obj/item/stack/medical) && !istype(O, /obj/item/reagent_containers/food/snacks/cracker)) - 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 - if(health > 30) //Let's get in there and squawk it up! - parrot_state |= PARROT_ATTACK - else - parrot_state |= PARROT_FLEE - icon_state = icon_living - drop_held_item(0) - else if(istype(O, /obj/item/reagent_containers/food/snacks/cracker)) //Poly wants a cracker. - qdel(O) - if(health < maxHealth) - adjustBruteLoss(-10) - speak_chance *= 1.27 // 20 crackers to go from 1% to 100% - speech_shuffle_rate += 10 - to_chat(user, "[src] eagerly devours the cracker.") - return // the cracker was deleted - return ..() - -//Bullets -/mob/living/simple_animal/parrot/bullet_act(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 | PARROT_FLEE //Been shot and survived! RUN LIKE HELL! - //parrot_been_shot += 5 - icon_state = icon_living - drop_held_item(0) - return - - -/* - * AI - Not really intelligent, but I'm calling it AI anyway. - */ -/mob/living/simple_animal/parrot/Life() - ..() - - //Sprite update for when a parrot gets pulled - if(pulledby && !stat && parrot_state != PARROT_WANDER) - if(buckled) - buckled.unbuckle_mob(src, TRUE) - buckled = null - icon_state = icon_living - parrot_state = PARROT_WANDER - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - return - - -//-----SPEECH - /* Parrot speech mimickry! - Phrases that the parrot Hear()s 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. */ -/mob/living/simple_animal/parrot/handle_automated_speech() - ..() - if(speech_buffer.len && prob(speech_shuffle_rate)) //shuffle out a phrase and add in a new one - if(speak.len) - speak.Remove(pick(speak)) - - speak.Add(pick(speech_buffer)) - - -/mob/living/simple_animal/parrot/handle_automated_movement() - 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) - - if(client && stat == CONSCIOUS && parrot_state != icon_living) - icon_state = icon_living - //Because the most appropriate place to set icon_state is movement_delay(), clearly - -//-----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 = icon_living - return - else - parrot_state = PARROT_WANDER - icon_state = icon_living - 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 - if(speak.len) - var/list/newspeak = list() - - if(available_channels.len && src.ears) - for(var/possible_phrase in speak) - - //50/50 chance to not use the radio at all - var/useradio = 0 - if(prob(50)) - useradio = 1 - - if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) - possible_phrase = "[useradio?pick(available_channels):""][copytext(possible_phrase,3)]" //crop out the channel prefix - else - possible_phrase = "[useradio?pick(available_channels):""][possible_phrase]" - - newspeak.Add(possible_phrase) - - else //If we have no headset or channels to use, dont try to use any! - for(var/possible_phrase in speak) - if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) - possible_phrase = copytext(possible_phrase,3) //crop out the channel prefix - newspeak.Add(possible_phrase) - speak = newspeak - - //Search for item to steal - parrot_interest = search_for_item() - if(parrot_interest) - emote("me", 1, "looks in [parrot_interest]'s direction and takes flight.") - parrot_state = PARROT_SWOOP | PARROT_STEAL - icon_state = icon_living - 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(GLOB.cardinals)) - 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 - emote("me", 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(Adjacent(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.forceMove(src) - visible_message("[src] grabs [held_item]!", "You grab [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) - if(isStuck()) - return - - 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(Adjacent(parrot_perch)) - forceMove(parrot_perch.loc) - drop_held_item() - parrot_state = PARROT_PERCH - icon_state = icon_sit - return - - walk_to(src, parrot_perch, 1, parrot_speed) - if(isStuck()) - return - - 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) - if(isStuck()) - return - - 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(melee_damage_upper == 0) - melee_damage_upper = parrot_damage_upper - a_intent = INTENT_HARM - - //If the mob is close enough to interact with - if(Adjacent(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 - - attacktext = pick("claws at", "chomps") - L.attack_animal(src)//Time for the hurt to begin! - //Otherwise, fly towards the mob! - else - walk_to(src, parrot_interest, 1, parrot_speed) - if(isStuck()) - return - - 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/proc/isStuck() - //Check to see if the parrot is stuck due to things like windows or doors or windowdoors - if(parrot_lastmove) - if(parrot_lastmove == src.loc) - if(parrot_stuck_threshold >= ++parrot_stuck) //If it has been stuck for a while, go back to wander. - parrot_state = PARROT_WANDER - parrot_stuck = 0 - parrot_lastmove = null - return 1 - else - parrot_lastmove = null - else - parrot_lastmove = src.loc - return 0 - -/mob/living/simple_animal/parrot/proc/search_for_item() - var/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) - item = I - else if(iscarbon(AM)) - var/mob/living/carbon/C = AM - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - item = I - break - if(item) - if(!AStar(src, get_turf(item), /turf/proc/Distance_cardinal)) - item = null - continue - return item - - 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 - for(var/obj/item/I in C.held_items) - if(I.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 [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.forceMove(src) - visible_message("[src] grabs [held_item]!", "You grab [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 [held_item]!") - return 1 - - var/obj/item/stolen_item = null - - for(var/mob/living/carbon/C in view(1,src)) - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - stolen_item = I - break - - if(stolen_item) - C.transferItemToLoc(stolen_item, src, TRUE) - held_item = stolen_item - visible_message("[src] grabs [held_item] out of [C]'s hand!", "You snag [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(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) - if(src == usr) //So that other mobs wont make this message appear when they're bludgeoning you. - to_chat(src, "You have nothing to drop!") - return 0 - - -//parrots will eat crackers instead of dropping them - if(istype(held_item, /obj/item/reagent_containers/food/snacks/cracker) && (drop_gently)) - qdel(held_item) - held_item = null - if(health < maxHealth) - adjustBruteLoss(-10) - emote("me", 1, "[src] eagerly downs the cracker.") - return 1 - - - if(!drop_gently) - if(istype(held_item, /obj/item/grenade)) - var/obj/item/grenade/G = held_item - G.forceMove(drop_location()) - G.prime() - to_chat(src, "You let go of [held_item]!") - held_item = null - return 1 - - to_chat(src, "You drop [held_item].") - - held_item.forceMove(drop_location()) - 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 == icon_living) - for(var/atom/movable/AM in view(src,1)) - for(var/perch_path in desired_perches) - if(istype(AM, perch_path)) - src.forceMove(AM.loc) - icon_state = icon_sit - parrot_state = PARROT_PERCH - return - to_chat(src, "There is no perch nearby to sit on!") - return - -/mob/living/simple_animal/parrot/Moved(oldLoc, dir) - . = ..() - if(. && !stat && client && parrot_state == PARROT_PERCH) - parrot_state = PARROT_WANDER - icon_state = icon_living - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - -/mob/living/simple_animal/parrot/proc/perch_mob_player() - set name = "Sit on Human's Shoulder" - set category = "Parrot" - set desc = "Sit on a nice comfy human being!" - - if(stat || !client) - return - - if(!buckled) - for(var/mob/living/carbon/human/H in view(src,1)) - if(H.has_buckled_mobs() && H.buckled_mobs.len >= H.max_buckled_mobs) //Already has a parrot, or is being eaten by a slime - continue - perch_on_human(H) - return - to_chat(src, "There is nobody nearby that you can sit on!") - else - icon_state = icon_living - parrot_state = PARROT_WANDER - if(buckled) - to_chat(src, "You are no longer sitting on [buckled]'s shoulder.") - buckled.unbuckle_mob(src, TRUE) - buckled = null - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - - - -/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/H) - if(!H) - return - forceMove(get_turf(H)) - if(H.buckle_mob(src, TRUE)) - pixel_y = 9 - pixel_x = pick(-8,8) //pick left or right shoulder - icon_state = icon_sit - parrot_state = PARROT_PERCH - to_chat(src, "You sit on [H]'s shoulder.") - - -/mob/living/simple_animal/parrot/proc/toggle_mode() - set name = "Toggle mode" - set category = "Parrot" - set desc = "Time to bear those claws!" - - if(stat || !client) - return - - if(a_intent != INTENT_HELP) - melee_damage_upper = 0 - a_intent = INTENT_HELP - else - melee_damage_upper = parrot_damage_upper - a_intent = INTENT_HARM - to_chat(src, "You will now [a_intent] others.") - return - -/* - * Sub-types - */ -/mob/living/simple_animal/parrot/Poly - name = "Poly" - desc = "Poly the Parrot. An expert on quantum cracker theory." - speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE") - gold_core_spawnable = NO_SPAWN - speak_chance = 3 - var/memory_saved = FALSE - var/rounds_survived = 0 - var/longest_survival = 0 - var/longest_deathstreak = 0 - -/mob/living/simple_animal/parrot/Poly/Initialize() - ears = new /obj/item/radio/headset/headset_eng(src) - available_channels = list(":e") - Read_Memory() - if(rounds_survived == longest_survival) - speak += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?") - desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]." - speak_chance = 20 //His hubris has made him more annoying/easier to justify killing - add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY) - else if(rounds_survived == longest_deathstreak) - speak += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!") - desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..." - add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY) - else if(rounds_survived > 0) - speak += pick("...again?", "No, It was over!", "Let me out!", "It never ends!") - desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!" - else - speak += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!") - - . = ..() - -/mob/living/simple_animal/parrot/Poly/Life() - if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) - Write_Memory(FALSE) - memory_saved = TRUE - ..() - -/mob/living/simple_animal/parrot/Poly/death(gibbed) - if(!memory_saved) - Write_Memory(TRUE) - if(rounds_survived == longest_survival || rounds_survived == longest_deathstreak || prob(0.666)) - var/mob/living/simple_animal/parrot/Poly/ghost/G = new(loc) - if(mind) - mind.transfer_to(G) - else - G.key = key - ..(gibbed) - -/mob/living/simple_animal/parrot/Poly/proc/Read_Memory() - if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new - var/savefile/S = new /savefile("data/npc_saves/Poly.sav") - S["phrases"] >> speech_buffer - S["roundssurvived"] >> rounds_survived - S["longestsurvival"] >> longest_survival - S["longestdeathstreak"] >> longest_deathstreak - fdel("data/npc_saves/Poly.sav") - else - var/json_file = file("data/npc_saves/Poly.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - speech_buffer = json["phrases"] - rounds_survived = json["roundssurvived"] - longest_survival = json["longestsurvival"] - longest_deathstreak = json["longestdeathstreak"] - if(!islist(speech_buffer)) - speech_buffer = list() - -/mob/living/simple_animal/parrot/Poly/proc/Write_Memory(dead) - var/json_file = file("data/npc_saves/Poly.json") - var/list/file_data = list() - if(islist(speech_buffer)) - file_data["phrases"] = speech_buffer - if(dead) - file_data["roundssurvived"] = min(rounds_survived - 1, 0) - file_data["longestsurvival"] = longest_survival - if(rounds_survived - 1 < longest_deathstreak) - file_data["longestdeathstreak"] = rounds_survived - 1 - else - file_data["longestdeathstreak"] = longest_deathstreak - else - file_data["roundssurvived"] = rounds_survived + 1 - if(rounds_survived + 1 > longest_survival) - file_data["longestsurvival"] = rounds_survived + 1 - else - file_data["longestsurvival"] = longest_survival - file_data["longestdeathstreak"] = longest_deathstreak - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/mob/living/simple_animal/parrot/Poly/ratvar_act() - playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 75, TRUE) - var/mob/living/simple_animal/parrot/clock_hawk/H = new(loc) - H.setDir(dir) - qdel(src) - -/mob/living/simple_animal/parrot/Poly/ghost - name = "The Ghost of Poly" - desc = "Doomed to squawk the Earth." - color = "#FFFFFF77" - speak_chance = 20 - status_flags = GODMODE - incorporeal_move = INCORPOREAL_MOVE_BASIC - butcher_results = list(/obj/item/ectoplasm = 1) - -/mob/living/simple_animal/parrot/Poly/ghost/Initialize() - memory_saved = TRUE //At this point nothing is saved - . = ..() - -/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_speech() - if(ismob(loc)) - return - ..() - -/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_movement() - if(isliving(parrot_interest)) - if(!ishuman(parrot_interest)) - parrot_interest = null - else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest)) - walk_to(src, parrot_interest, 0, parrot_speed) - Possess(parrot_interest) - ..() - -/mob/living/simple_animal/parrot/Poly/ghost/proc/Possess(mob/living/carbon/human/H) - if(!ishuman(H)) - return - var/datum/disease/parrot_possession/P = new - P.parrot = src - forceMove(H) - H.ForceContractDisease(P) - parrot_interest = null - H.visible_message("[src] dive bombs into [H]'s chest and vanishes!", "[src] dive bombs into your chest, vanishing! This can't be good!") - - -/mob/living/simple_animal/parrot/clock_hawk - name = "clock hawk" - desc = "Cbyl jnaan penpxre! Fdhnnnjx!" - icon_state = "clock_hawk_fly" - icon_living = "clock_hawk_fly" - icon_sit = "clock_hawk_sit" - speak = list("Penpxre!", "Ratvar vf n qhzo anzr naljnl!") - speak_emote = list("squawks rustily", "says crassly", "yells brassly") - emote_hear = list("squawks rustily.", "bawks metallically!") - emote_see = list("flutters its metal wings.") - faction = list("ratvar") - gold_core_spawnable = NO_SPAWN - del_on_death = TRUE - death_sound = 'sound/magic/clockwork/anima_fragment_death.ogg' - -/mob/living/simple_animal/parrot/clock_hawk/ratvar_act() - return +/* Parrots! + * Contains + * Defines + * Inventory (headset stuff) + * Attack responces + * AI + * Procs / Verbs (usable by players) + * Sub-types + * Hear & say (the things we do for gimmicks) + */ + +/* + * Defines + */ + +//Only a maximum of one action and one intent should be active at any given time. +//Actions +#define PARROT_PERCH (1<<0) //Sitting/sleeping, not moving +#define PARROT_SWOOP (1<<1) //Moving towards or away from a target +#define PARROT_WANDER (1<<2) //Moving without a specific target in mind + +//Intents +#define PARROT_STEAL (1<<3) //Flying towards a target to steal it/from it +#define PARROT_ATTACK (1<<4) //Flying towards a target to attack it +#define PARROT_RETURN (1<<5) //Flying towards its perch +#define PARROT_FLEE (1<<6) //Flying away from its attacker + + +/mob/living/simple_animal/parrot + name = "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" + var/icon_sit = "parrot_sit" + density = FALSE + health = 80 + maxHealth = 80 + pass_flags = PASSTABLE | PASSMOB + + 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/ = 1) + melee_damage_upper = 10 + melee_damage_lower = 5 + + response_help = "pets" + response_disarm = "gently moves aside" + response_harm = "swats" + stop_automated_movement = 1 + a_intent = INTENT_HARM //parrots now start "aggressive" since only player parrots will nuzzle. + attacktext = "chomps" + friendly = "grooms" + mob_size = MOB_SIZE_SMALL + movement_type = FLYING + gold_core_spawnable = FRIENDLY_SPAWN + + var/parrot_damage_upper = 10 + 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 life() 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(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_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_lastmove = null //Updates/Stores position of the parrot while it's moving + var/parrot_stuck = 0 //If parrot_lastmove hasnt changed, this will increment until it reaches parrot_stuck_threshold + var/parrot_stuck_threshold = 10 //if this == parrot_stuck, it'll force the parrot back to wandering + + var/list/speech_buffer = list() + var/speech_shuffle_rate = 20 + 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 perch 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/frame/computer, /obj/structure/displaycase, \ + /obj/structure/filingcabinet, /obj/machinery/teleport, \ + /obj/machinery/computer, /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/suit_storage_unit) + + //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. + var/obj/item/held_item = null + + +/mob/living/simple_animal/parrot/Initialize() + . = ..() + 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) + + 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/proc/toggle_mode, + /mob/living/simple_animal/parrot/proc/perch_mob_player) + + +/mob/living/simple_animal/parrot/examine(mob/user) + . = ..() + if(stat) + . += pick("This parrot is no more.", "This is a late parrot.", "This is an ex-parrot.") + +/mob/living/simple_animal/parrot/death(gibbed) + if(held_item) + held_item.forceMove(drop_location()) + held_item = null + walk(src,0) + + if(buckled) + buckled.unbuckle_mob(src,force=1) + buckled = null + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + + ..(gibbed) + +/mob/living/simple_animal/parrot/Stat() + ..() + if(statpanel("Status")) + stat("Held Item", held_item) + stat("Mode",a_intent) + +/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(speaker != src && prob(50)) //Dont imitate ourselves + if(!radio_freq || prob(10)) + if(speech_buffer.len >= 500) + speech_buffer -= pick(speech_buffer) + speech_buffer |= html_decode(raw_message) + if(speaker == src && !client) //If a parrot squawks in the woods and no one is around to hear it, does it make a sound? This code says yes! + return message + +/mob/living/simple_animal/parrot/radio(message, message_mode, list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be. + . = ..() + if(. != 0) + return . + + switch(message_mode) + if(MODE_HEADSET) + if (ears) + ears.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_DEPARTMENT) + if (ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + if(message_mode in GLOB.radiochannels) + if(ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + return 0 + +/* + * Inventory + */ +/mob/living/simple_animal/parrot/show_inv(mob/user) + user.set_machine(src) + + var/dat = "

Inventory of [name]

" + dat += "
Headset: [ears]" : "add_inv=ears'>Nothing"]" + + user << browse(dat, "window=mob[REF(src)];size=325x500") + onclose(user, "window=mob[REF(src)]") + + +/mob/living/simple_animal/parrot/Topic(href, href_list) + if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + usr << browse(null, "window=mob[REF(src)]") + usr.unset_machine() + return + + //Removing from inventory + if(href_list["remove_inv"]) + var/remove_from = href_list["remove_inv"] + switch(remove_from) + if("ears") + if(!ears) + to_chat(usr, "There is nothing to remove from its [remove_from]!") + return + if(!stat) + say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") + ears.forceMove(drop_location()) + ears = null + for(var/possible_phrase in speak) + if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys) + possible_phrase = copytext(possible_phrase,3) + + //Adding things to inventory + else if(href_list["add_inv"]) + var/add_to = href_list["add_inv"] + if(!usr.get_active_held_item()) + 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_held_item() + 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 + + if(!usr.transferItemToLoc(headset_to_add, src)) + return + ears = headset_to_add + to_chat(usr, "You fit the headset onto [src].") + + clearlist(available_channels) + for(var/ch in headset_to_add.channels) + switch(ch) + if(RADIO_CHANNEL_ENGINEERING) + available_channels.Add(RADIO_TOKEN_ENGINEERING) + if(RADIO_CHANNEL_COMMAND) + available_channels.Add(RADIO_TOKEN_COMMAND) + if(RADIO_CHANNEL_SECURITY) + available_channels.Add(RADIO_TOKEN_SECURITY) + if(RADIO_CHANNEL_SCIENCE) + available_channels.Add(RADIO_TOKEN_SCIENCE) + if(RADIO_CHANNEL_MEDICAL) + available_channels.Add(RADIO_TOKEN_MEDICAL) + if(RADIO_CHANNEL_SUPPLY) + available_channels.Add(RADIO_TOKEN_SUPPLY) + if(RADIO_CHANNEL_SERVICE) + available_channels.Add(RADIO_TOKEN_SERVICE) + + if(headset_to_add.translate_binary) + available_channels.Add(MODE_TOKEN_BINARY) + else + return ..() + + +/* + * Attack responces + */ +//Humans, monkeys, aliens +/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M) + ..() + if(client) + return + if(!stat && M.a_intent == INTENT_HARM) + + icon_state = icon_living //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(health > 30) //Let's get in there and squawk it up! + parrot_state |= PARROT_ATTACK + else + parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! + drop_held_item(0) + if(stat != DEAD && M.a_intent == INTENT_HELP) + handle_automated_speech(1) //assured speak/emote + return + +/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/monkey/M) + return attack_hand(M) + +/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/alien/M) + return attack_hand(M) + +//Simple animals +/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/M) + . = ..() //goodbye immortal parrots + + if(client) + return + + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + if(M.melee_damage_upper > 0 && !stat) + parrot_interest = M + parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless + icon_state = icon_living + +//Mobs with objects +/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/living/user, params) + if(!stat && !client && !istype(O, /obj/item/stack/medical) && !istype(O, /obj/item/reagent_containers/food/snacks/cracker)) + 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 + if(health > 30) //Let's get in there and squawk it up! + parrot_state |= PARROT_ATTACK + else + parrot_state |= PARROT_FLEE + icon_state = icon_living + drop_held_item(0) + else if(istype(O, /obj/item/reagent_containers/food/snacks/cracker)) //Poly wants a cracker. + qdel(O) + if(health < maxHealth) + adjustBruteLoss(-10) + speak_chance *= 1.27 // 20 crackers to go from 1% to 100% + speech_shuffle_rate += 10 + to_chat(user, "[src] eagerly devours the cracker.") + return // the cracker was deleted + return ..() + +//Bullets +/mob/living/simple_animal/parrot/bullet_act(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 | PARROT_FLEE //Been shot and survived! RUN LIKE HELL! + //parrot_been_shot += 5 + icon_state = icon_living + drop_held_item(0) + return + + +/* + * AI - Not really intelligent, but I'm calling it AI anyway. + */ +/mob/living/simple_animal/parrot/Life() + ..() + + //Sprite update for when a parrot gets pulled + if(pulledby && !stat && parrot_state != PARROT_WANDER) + if(buckled) + buckled.unbuckle_mob(src, TRUE) + buckled = null + icon_state = icon_living + parrot_state = PARROT_WANDER + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + return + + +//-----SPEECH + /* Parrot speech mimickry! + Phrases that the parrot Hear()s 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. */ +/mob/living/simple_animal/parrot/handle_automated_speech() + ..() + if(speech_buffer.len && prob(speech_shuffle_rate)) //shuffle out a phrase and add in a new one + if(speak.len) + speak.Remove(pick(speak)) + + speak.Add(pick(speech_buffer)) + + +/mob/living/simple_animal/parrot/handle_automated_movement() + 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) + + if(client && stat == CONSCIOUS && parrot_state != icon_living) + icon_state = icon_living + //Because the most appropriate place to set icon_state is movement_delay(), clearly + +//-----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 = icon_living + return + else + parrot_state = PARROT_WANDER + icon_state = icon_living + 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 + if(speak.len) + var/list/newspeak = list() + + if(available_channels.len && src.ears) + for(var/possible_phrase in speak) + + //50/50 chance to not use the radio at all + var/useradio = 0 + if(prob(50)) + useradio = 1 + + if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) + possible_phrase = "[useradio?pick(available_channels):""][copytext(possible_phrase,3)]" //crop out the channel prefix + else + possible_phrase = "[useradio?pick(available_channels):""][possible_phrase]" + + newspeak.Add(possible_phrase) + + else //If we have no headset or channels to use, dont try to use any! + for(var/possible_phrase in speak) + if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) + possible_phrase = copytext(possible_phrase,3) //crop out the channel prefix + newspeak.Add(possible_phrase) + speak = newspeak + + //Search for item to steal + parrot_interest = search_for_item() + if(parrot_interest) + emote("me", 1, "looks in [parrot_interest]'s direction and takes flight.") + parrot_state = PARROT_SWOOP | PARROT_STEAL + icon_state = icon_living + 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(GLOB.cardinals)) + 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 + emote("me", 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(Adjacent(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.forceMove(src) + visible_message("[src] grabs [held_item]!", "You grab [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) + if(isStuck()) + return + + 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(Adjacent(parrot_perch)) + forceMove(parrot_perch.loc) + drop_held_item() + parrot_state = PARROT_PERCH + icon_state = icon_sit + return + + walk_to(src, parrot_perch, 1, parrot_speed) + if(isStuck()) + return + + 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) + if(isStuck()) + return + + 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(melee_damage_upper == 0) + melee_damage_upper = parrot_damage_upper + a_intent = INTENT_HARM + + //If the mob is close enough to interact with + if(Adjacent(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 + + attacktext = pick("claws at", "chomps") + L.attack_animal(src)//Time for the hurt to begin! + //Otherwise, fly towards the mob! + else + walk_to(src, parrot_interest, 1, parrot_speed) + if(isStuck()) + return + + 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/proc/isStuck() + //Check to see if the parrot is stuck due to things like windows or doors or windowdoors + if(parrot_lastmove) + if(parrot_lastmove == src.loc) + if(parrot_stuck_threshold >= ++parrot_stuck) //If it has been stuck for a while, go back to wander. + parrot_state = PARROT_WANDER + parrot_stuck = 0 + parrot_lastmove = null + return 1 + else + parrot_lastmove = null + else + parrot_lastmove = src.loc + return 0 + +/mob/living/simple_animal/parrot/proc/search_for_item() + var/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) + item = I + else if(iscarbon(AM)) + var/mob/living/carbon/C = AM + for(var/obj/item/I in C.held_items) + if(I.w_class <= WEIGHT_CLASS_SMALL) + item = I + break + if(item) + if(!AStar(src, get_turf(item), /turf/proc/Distance_cardinal)) + item = null + continue + return item + + 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 + for(var/obj/item/I in C.held_items) + if(I.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 [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.forceMove(src) + visible_message("[src] grabs [held_item]!", "You grab [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 [held_item]!") + return 1 + + var/obj/item/stolen_item = null + + for(var/mob/living/carbon/C in view(1,src)) + for(var/obj/item/I in C.held_items) + if(I.w_class <= WEIGHT_CLASS_SMALL) + stolen_item = I + break + + if(stolen_item) + C.transferItemToLoc(stolen_item, src, TRUE) + held_item = stolen_item + visible_message("[src] grabs [held_item] out of [C]'s hand!", "You snag [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(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) + if(src == usr) //So that other mobs wont make this message appear when they're bludgeoning you. + to_chat(src, "You have nothing to drop!") + return 0 + + +//parrots will eat crackers instead of dropping them + if(istype(held_item, /obj/item/reagent_containers/food/snacks/cracker) && (drop_gently)) + qdel(held_item) + held_item = null + if(health < maxHealth) + adjustBruteLoss(-10) + emote("me", 1, "[src] eagerly downs the cracker.") + return 1 + + + if(!drop_gently) + if(istype(held_item, /obj/item/grenade)) + var/obj/item/grenade/G = held_item + G.forceMove(drop_location()) + G.prime() + to_chat(src, "You let go of [held_item]!") + held_item = null + return 1 + + to_chat(src, "You drop [held_item].") + + held_item.forceMove(drop_location()) + 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 == icon_living) + for(var/atom/movable/AM in view(src,1)) + for(var/perch_path in desired_perches) + if(istype(AM, perch_path)) + src.forceMove(AM.loc) + icon_state = icon_sit + parrot_state = PARROT_PERCH + return + to_chat(src, "There is no perch nearby to sit on!") + return + +/mob/living/simple_animal/parrot/Moved(oldLoc, dir) + . = ..() + if(. && !stat && client && parrot_state == PARROT_PERCH) + parrot_state = PARROT_WANDER + icon_state = icon_living + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + +/mob/living/simple_animal/parrot/proc/perch_mob_player() + set name = "Sit on Human's Shoulder" + set category = "Parrot" + set desc = "Sit on a nice comfy human being!" + + if(stat || !client) + return + + if(!buckled) + for(var/mob/living/carbon/human/H in view(src,1)) + if(H.has_buckled_mobs() && H.buckled_mobs.len >= H.max_buckled_mobs) //Already has a parrot, or is being eaten by a slime + continue + perch_on_human(H) + return + to_chat(src, "There is nobody nearby that you can sit on!") + else + icon_state = icon_living + parrot_state = PARROT_WANDER + if(buckled) + to_chat(src, "You are no longer sitting on [buckled]'s shoulder.") + buckled.unbuckle_mob(src, TRUE) + buckled = null + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + + + +/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/H) + if(!H) + return + forceMove(get_turf(H)) + if(H.buckle_mob(src, TRUE)) + pixel_y = 9 + pixel_x = pick(-8,8) //pick left or right shoulder + icon_state = icon_sit + parrot_state = PARROT_PERCH + to_chat(src, "You sit on [H]'s shoulder.") + + +/mob/living/simple_animal/parrot/proc/toggle_mode() + set name = "Toggle mode" + set category = "Parrot" + set desc = "Time to bear those claws!" + + if(stat || !client) + return + + if(a_intent != INTENT_HELP) + melee_damage_upper = 0 + a_intent = INTENT_HELP + else + melee_damage_upper = parrot_damage_upper + a_intent = INTENT_HARM + to_chat(src, "You will now [a_intent] others.") + return + +/* + * Sub-types + */ +/mob/living/simple_animal/parrot/Poly + name = "Poly" + desc = "Poly the Parrot. An expert on quantum cracker theory." + speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE") + gold_core_spawnable = NO_SPAWN + speak_chance = 3 + var/memory_saved = FALSE + var/rounds_survived = 0 + var/longest_survival = 0 + var/longest_deathstreak = 0 + +/mob/living/simple_animal/parrot/Poly/Initialize() + ears = new /obj/item/radio/headset/headset_eng(src) + available_channels = list(":e") + Read_Memory() + if(rounds_survived == longest_survival) + speak += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?") + desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]." + speak_chance = 20 //His hubris has made him more annoying/easier to justify killing + add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY) + else if(rounds_survived == longest_deathstreak) + speak += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!") + desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..." + add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY) + else if(rounds_survived > 0) + speak += pick("...again?", "No, It was over!", "Let me out!", "It never ends!") + desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!" + else + speak += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!") + + . = ..() + +/mob/living/simple_animal/parrot/Poly/Life() + if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) + Write_Memory(FALSE) + memory_saved = TRUE + ..() + +/mob/living/simple_animal/parrot/Poly/death(gibbed) + if(!memory_saved) + Write_Memory(TRUE) + if(rounds_survived == longest_survival || rounds_survived == longest_deathstreak || prob(0.666)) + var/mob/living/simple_animal/parrot/Poly/ghost/G = new(loc) + if(mind) + mind.transfer_to(G) + else + G.key = key + ..(gibbed) + +/mob/living/simple_animal/parrot/Poly/proc/Read_Memory() + if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new + var/savefile/S = new /savefile("data/npc_saves/Poly.sav") + S["phrases"] >> speech_buffer + S["roundssurvived"] >> rounds_survived + S["longestsurvival"] >> longest_survival + S["longestdeathstreak"] >> longest_deathstreak + fdel("data/npc_saves/Poly.sav") + else + var/json_file = file("data/npc_saves/Poly.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + speech_buffer = json["phrases"] + rounds_survived = json["roundssurvived"] + longest_survival = json["longestsurvival"] + longest_deathstreak = json["longestdeathstreak"] + if(!islist(speech_buffer)) + speech_buffer = list() + +/mob/living/simple_animal/parrot/Poly/proc/Write_Memory(dead) + var/json_file = file("data/npc_saves/Poly.json") + var/list/file_data = list() + if(islist(speech_buffer)) + file_data["phrases"] = speech_buffer + if(dead) + file_data["roundssurvived"] = min(rounds_survived - 1, 0) + file_data["longestsurvival"] = longest_survival + if(rounds_survived - 1 < longest_deathstreak) + file_data["longestdeathstreak"] = rounds_survived - 1 + else + file_data["longestdeathstreak"] = longest_deathstreak + else + file_data["roundssurvived"] = rounds_survived + 1 + if(rounds_survived + 1 > longest_survival) + file_data["longestsurvival"] = rounds_survived + 1 + else + file_data["longestsurvival"] = longest_survival + file_data["longestdeathstreak"] = longest_deathstreak + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/mob/living/simple_animal/parrot/Poly/ratvar_act() + playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 75, TRUE) + var/mob/living/simple_animal/parrot/clock_hawk/H = new(loc) + H.setDir(dir) + qdel(src) + +/mob/living/simple_animal/parrot/Poly/ghost + name = "The Ghost of Poly" + desc = "Doomed to squawk the Earth." + color = "#FFFFFF77" + speak_chance = 20 + status_flags = GODMODE + incorporeal_move = INCORPOREAL_MOVE_BASIC + butcher_results = list(/obj/item/ectoplasm = 1) + +/mob/living/simple_animal/parrot/Poly/ghost/Initialize() + memory_saved = TRUE //At this point nothing is saved + . = ..() + +/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_speech() + if(ismob(loc)) + return + ..() + +/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_movement() + if(isliving(parrot_interest)) + if(!ishuman(parrot_interest)) + parrot_interest = null + else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest)) + walk_to(src, parrot_interest, 0, parrot_speed) + Possess(parrot_interest) + ..() + +/mob/living/simple_animal/parrot/Poly/ghost/proc/Possess(mob/living/carbon/human/H) + if(!ishuman(H)) + return + var/datum/disease/parrot_possession/P = new + P.parrot = src + forceMove(H) + H.ForceContractDisease(P) + parrot_interest = null + H.visible_message("[src] dive bombs into [H]'s chest and vanishes!", "[src] dive bombs into your chest, vanishing! This can't be good!") + + +/mob/living/simple_animal/parrot/clock_hawk + name = "clock hawk" + desc = "Cbyl jnaan penpxre! Fdhnnnjx!" + icon_state = "clock_hawk_fly" + icon_living = "clock_hawk_fly" + icon_sit = "clock_hawk_sit" + speak = list("Penpxre!", "Ratvar vf n qhzo anzr naljnl!") + speak_emote = list("squawks rustily", "says crassly", "yells brassly") + emote_hear = list("squawks rustily.", "bawks metallically!") + emote_see = list("flutters its metal wings.") + faction = list("ratvar") + gold_core_spawnable = NO_SPAWN + del_on_death = TRUE + death_sound = 'sound/magic/clockwork/anima_fragment_death.ogg' + +/mob/living/simple_animal/parrot/clock_hawk/ratvar_act() + return diff --git a/code/modules/mob/living/simple_animal/slime/say.dm b/code/modules/mob/living/simple_animal/slime/say.dm index a2618b71..c48249d8 100644 --- a/code/modules/mob/living/simple_animal/slime/say.dm +++ b/code/modules/mob/living/simple_animal/slime/say.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/slime/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) +/mob/living/simple_animal/slime/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source) . = ..() if(speaker != src && !radio_freq && !stat) if (speaker in Friends) diff --git a/modular_citadel/code/modules/mob/mob.dm b/modular_citadel/code/modules/mob/mob.dm index e2e90ce9..b81344c5 100644 --- a/modular_citadel/code/modules/mob/mob.dm +++ b/modular_citadel/code/modules/mob/mob.dm @@ -16,13 +16,13 @@ else return -/mob/living/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE) +/mob/living/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE, atom/movable/source) . = ..() - if(istype(speaker, /mob/living)) - var/turf/speakturf = get_turf(speaker) - var/turf/sourceturf = get_turf(src) - if(istype(speakturf) && istype(sourceturf) && !(speakturf in get_hear(5, sourceturf))) - . = "[.]" //Don't ask how the fuck this works. It just does. + if(isliving(speaker)) + var/turf/sourceturf = get_turf(source) + var/turf/T = get_turf(src) + if(sourceturf && T && !(sourceturf in get_hear(5, T))) + . = "[.]" /mob/verb/eastshift() set hidden = TRUE From 6ef6d21a846369c41ae93b89ec2588d9882a5003 Mon Sep 17 00:00:00 2001 From: Lin Date: Thu, 19 Dec 2019 23:03:48 +0000 Subject: [PATCH 09/21] Merge pull request #9722 from Ghommie/Ghommie-cit320 Umodularize saycode and meat slabs. --- code/__DEFINES/say.dm | 4 +- code/game/say.dm | 10 +- .../food_and_drinks/food/snacks/meat.dm | 22 ++ code/modules/mob/living/carbon/human/say.dm | 18 +- code/modules/mob/living/say.dm | 8 + code/modules/mob/mob.dm | 35 ++- code/modules/mob/say.dm | 268 +++++++++--------- icons/obj/food/food.dmi | Bin 73765 -> 74106 bytes .../code/game/objects/items/meat.dm | 26 -- .../modules/food_and_drinks/snacks/meat.dm | 3 - modular_citadel/code/modules/mob/mob.dm | 57 ---- modular_citadel/icons/obj/foods.dmi | Bin 898 -> 0 bytes tgstation.dme | 3 - 13 files changed, 220 insertions(+), 234 deletions(-) delete mode 100644 modular_citadel/code/game/objects/items/meat.dm delete mode 100644 modular_citadel/code/modules/food_and_drinks/snacks/meat.dm delete mode 100644 modular_citadel/code/modules/mob/mob.dm delete mode 100644 modular_citadel/icons/obj/foods.dmi diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index 05d31154..80356bec 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -23,6 +23,8 @@ #define MODE_WHISPER "whisper" #define MODE_WHISPER_CRIT "whispercrit" +#define MODE_CUSTOM_SAY "custom_say" + #define MODE_DEPARTMENT "department" #define MODE_KEY_DEPARTMENT "h" #define MODE_TOKEN_DEPARTMENT ":h" @@ -84,4 +86,4 @@ // Audio/Visual Flags. Used to determine what sense are required to notice a message. #define MSG_VISUAL (1<<0) -#define MSG_AUDIBLE (1<<1) \ No newline at end of file +#define MSG_AUDIBLE (1<<1) diff --git a/code/game/say.dm b/code/game/say.dm index d5166b64..10c4a698 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -138,8 +138,14 @@ GLOBAL_LIST_INIT(freqtospan, list( return returntext return "[copytext("[freq]", 1, 4)].[copytext("[freq]", 4, 5)]" -/proc/attach_spans(input, list/spans) - return "[message_spans_start(spans)][input]" +/atom/movable/proc/attach_spans(input, list/spans) + var/customsayverb = findtext(input, "*") + if(customsayverb) + input = capitalize(copytext(input, customsayverb+1)) + if(input) + return "[message_spans_start(spans)][input]" + else + return /proc/message_spans_start(list/spans) var/output = "" - else - return - -/mob/living/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE, atom/movable/source) - . = ..() - if(isliving(speaker)) - var/turf/sourceturf = get_turf(source) - var/turf/T = get_turf(src) - if(sourceturf && T && !(sourceturf in get_hear(5, T))) - . = "[.]" - -/mob/verb/eastshift() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_x <= 16) - pixel_x++ - is_shifted = TRUE - -/mob/verb/westshift() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_x >= -16) - pixel_x-- - is_shifted = TRUE - -/mob/verb/northshift() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_y <= 16) - pixel_y++ - is_shifted = TRUE - -/mob/verb/southshift() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_y >= -16) - pixel_y-- - is_shifted = TRUE \ No newline at end of file diff --git a/modular_citadel/icons/obj/foods.dmi b/modular_citadel/icons/obj/foods.dmi deleted file mode 100644 index 3349da820319d45b0d6de301a847349c6d169430..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 898 zcmV-|1AY97P)(^axw*NI>iz(A zkN|(28X6iRA|lt<*S+%pz`(%w*p}|suZm6-kyaGHiEDd76n8)r+_a$1mwcgeJf(JA zmTXjBFBES)6wa({i(pIRi<_$e0004WQchCV=- z0C=2JR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6#a*U0*I5Sc+(=$pSoZ^zi zl2jm5sW>||C$%I$FE=%@M2U+tC9|j)C~U~ZnO2mTn+jpW6@wHNr>15X6IAIL;7pu} z`6U@dDNM>NO2Ma4S;5uM1snnZ;+jl*U8L--0006VNklWG^hqvOE`L=hCO5QIbdC-e$ez?4ve37kZd*FXIOqEcgBGfkG`VUX&&G`!Nr-xP(+n1pRKuaxWEAc`hZ{`P@zv+x)v~Ax8{xsk++z8fvg##Gu z$JL%rZ8!gfuG$XG;Od*9(bv#Nf&l`k3;ju*uNsPn#x}!XNAlozz3E;3Z~9%s@q&oq zGJ3%1_g&wauAhGaH(k&03u!L;+50)a@1FWUL14%E`TO1dw_gLWV`BX4-_=J&Mg0rC Y18%4=gXPaHYXATM07*qoM6N<$f{%u$6#xJL diff --git a/tgstation.dme b/tgstation.dme index 5cebfa8f..3819cc83 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3116,7 +3116,6 @@ #include "modular_citadel\code\game\objects\items\boombox.dm" #include "modular_citadel\code\game\objects\items\holy_weapons.dm" #include "modular_citadel\code\game\objects\items\honk.dm" -#include "modular_citadel\code\game\objects\items\meat.dm" #include "modular_citadel\code\game\objects\items\stunsword.dm" #include "modular_citadel\code\game\objects\items\vending_items.dm" #include "modular_citadel\code\game\objects\items\circuitboards\machine_circuitboards.dm" @@ -3190,7 +3189,6 @@ #include "modular_citadel\code\modules\custom_loadout\read_from_file.dm" #include "modular_citadel\code\modules\events\blob.dm" #include "modular_citadel\code\modules\events\wizard\magicarp.dm" -#include "modular_citadel\code\modules\food_and_drinks\snacks\meat.dm" #include "modular_citadel\code\modules\integrated_electronics\subtypes\manipulation.dm" #include "modular_citadel\code\modules\jobs\dresscode_values.dm" #include "modular_citadel\code\modules\mentor\follow.dm" @@ -3202,7 +3200,6 @@ #include "modular_citadel\code\modules\mentor\mentorsay.dm" #include "modular_citadel\code\modules\mining\mining_ruins.dm" #include "modular_citadel\code\modules\mob\cit_emotes.dm" -#include "modular_citadel\code\modules\mob\mob.dm" #include "modular_citadel\code\modules\mob\dead\new_player\sprite_accessories.dm" #include "modular_citadel\code\modules\mob\living\damage_procs.dm" #include "modular_citadel\code\modules\mob\living\living.dm" From 19d6a19b4a6fa35969f5d5b1544d9afe6c29490b Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Thu, 24 Oct 2019 18:48:33 -0700 Subject: [PATCH 10/21] Merge pull request #9548 from Ghommie/Ghommie-cit251 Fixing radio emotes conflict with drunk memes. --- code/modules/mob/living/carbon/human/say.dm | 232 ++++++++++---------- code/modules/mob/living/say.dm | 20 +- code/modules/mob/mob_helpers.dm | 2 +- 3 files changed, 126 insertions(+), 128 deletions(-) diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm index 8e12ac97..c54f4101 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -1,116 +1,116 @@ -/mob/living/carbon/human/say_mod(input, message_mode) - verb_say = dna.species.say_mod - . = ..() - if(message_mode != MODE_CUSTOM_SAY && message_mode != MODE_WHISPER_CRIT) - switch(slurring) - if(10 to 25) - return "jumbles" - if(25 to 50) - return "slurs" - if(50 to INFINITY) - return "garbles" - -/mob/living/carbon/human/GetVoice() - if(istype(wear_mask, /obj/item/clothing/mask/chameleon)) - var/obj/item/clothing/mask/chameleon/V = wear_mask - if(V.vchange && wear_id) - var/obj/item/card/id/idcard = wear_id.GetID() - if(istype(idcard)) - return idcard.registered_name - else - return real_name - else - return real_name - if(mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling && changeling.mimicing ) - return changeling.mimicing - if(GetSpecialVoice()) - return GetSpecialVoice() - return real_name - -/mob/living/carbon/human/IsVocal() - // how do species that don't breathe talk? magic, that's what. - if(!HAS_TRAIT_FROM(src, TRAIT_NOBREATH, SPECIES_TRAIT) && !getorganslot(ORGAN_SLOT_LUNGS)) - return FALSE - if(mind) - return !mind.miming - return TRUE - -/mob/living/carbon/human/proc/SetSpecialVoice(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/binarycheck() - if(ears) - var/obj/item/radio/headset/dongle = ears - if(!istype(dongle)) - return FALSE - if(dongle.translate_binary) - return TRUE - -/mob/living/carbon/human/radio(message, message_mode, list/spans, language) - . = ..() - if(.) - return - - switch(message_mode) - if(MODE_HEADSET) - if (ears) - ears.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_DEPARTMENT) - if (ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - if(message_mode in GLOB.radiochannels) - if(ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - return 0 - -/mob/living/carbon/human/get_alt_name() - if(name != GetVoice()) - return " (as [get_id_name("Unknown")])" - -/mob/living/carbon/human/proc/forcesay(list/append) //this proc is at the bottom of the file because quote fuckery makes notepad++ cri - 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 - - 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_mod(input, message_mode) + verb_say = dna.species.say_mod + . = ..() + if(message_mode != MODE_CUSTOM_SAY && message_mode != MODE_WHISPER_CRIT) + switch(slurring) + if(10 to 25) + return "jumbles" + if(25 to 50) + return "slurs" + if(50 to INFINITY) + return "garbles" + +/mob/living/carbon/human/GetVoice() + if(istype(wear_mask, /obj/item/clothing/mask/chameleon)) + var/obj/item/clothing/mask/chameleon/V = wear_mask + if(V.vchange && wear_id) + var/obj/item/card/id/idcard = wear_id.GetID() + if(istype(idcard)) + return idcard.registered_name + else + return real_name + else + return real_name + if(mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling && changeling.mimicing ) + return changeling.mimicing + if(GetSpecialVoice()) + return GetSpecialVoice() + return real_name + +/mob/living/carbon/human/IsVocal() + // how do species that don't breathe talk? magic, that's what. + if(!HAS_TRAIT_FROM(src, TRAIT_NOBREATH, SPECIES_TRAIT) && !getorganslot(ORGAN_SLOT_LUNGS)) + return FALSE + if(mind) + return !mind.miming + return TRUE + +/mob/living/carbon/human/proc/SetSpecialVoice(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/binarycheck() + if(ears) + var/obj/item/radio/headset/dongle = ears + if(!istype(dongle)) + return FALSE + if(dongle.translate_binary) + return TRUE + +/mob/living/carbon/human/radio(message, message_mode, list/spans, language) + . = ..() + if(.) + return + + switch(message_mode) + if(MODE_HEADSET) + if (ears) + ears.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_DEPARTMENT) + if (ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + if(message_mode in GLOB.radiochannels) + if(ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + return 0 + +/mob/living/carbon/human/get_alt_name() + if(name != GetVoice()) + return " (as [get_id_name("Unknown")])" + +/mob/living/carbon/human/proc/forcesay(list/append) //this proc is at the bottom of the file because quote fuckery makes notepad++ cri + 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 + + var/trimmed = trim_left(temp) + if(length(trimmed)) + if(append) + temp += pick(append) + + say(temp) + winset(client, "input", "text=[null]") diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 3d0424f0..6e3ed2e3 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -413,18 +413,16 @@ GLOBAL_LIST_INIT(department_radio_keys, list( return 0 /mob/living/say_mod(input, message_mode) - if(message_mode == MODE_WHISPER) - . = verb_whisper - else if(message_mode == MODE_WHISPER_CRIT) + . = ..() + if(message_mode == MODE_WHISPER_CRIT) . = "[verb_whisper] in [p_their()] last breath" - else if(stuttering) - . = "stammers" - else if(derpspeech) - . = "gibbers" - else if(message_mode == MODE_SING) - . = verb_sing - else - . = ..() + else if(message_mode != MODE_CUSTOM_SAY) + if(message_mode == MODE_WHISPER) + . = verb_whisper + else if(stuttering) + . = "stammers" + else if(derpspeech) + . = "gibbers" /mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 7063ed51..b504f96d 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -88,7 +88,7 @@ if(newletter==" ") newletter="...huuuhhh..." if(newletter==".") - newletter=" *BURP*." + newletter=" BURP!" if(rand(1,100) <= strength*0.5) if(rand(1,5) == 1) newletter+="'" From 11515b3b32d3733423804769f2ef92cf1e335ed2 Mon Sep 17 00:00:00 2001 From: Archie Date: Tue, 5 Jan 2021 17:28:04 -0300 Subject: [PATCH 11/21] move mutations --- code/datums/{mutations/_mutations.dm => mutations.dm} | 0 tgstation.dme | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename code/datums/{mutations/_mutations.dm => mutations.dm} (100%) diff --git a/code/datums/mutations/_mutations.dm b/code/datums/mutations.dm similarity index 100% rename from code/datums/mutations/_mutations.dm rename to code/datums/mutations.dm diff --git a/tgstation.dme b/tgstation.dme index 3819cc83..823b3b4a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -324,6 +324,7 @@ #include "code\datums\martial.dm" #include "code\datums\mind.dm" #include "code\datums\mutable_appearance.dm" +#include "code\datums\mutations.dm" #include "code\datums\numbered_display.dm" #include "code\datums\outfit.dm" #include "code\datums\position_point_vector.dm" @@ -486,7 +487,6 @@ #include "code\datums\mood_events\generic_positive_events.dm" #include "code\datums\mood_events\mood_event.dm" #include "code\datums\mood_events\needs_events.dm" -#include "code\datums\mutations\_mutations.dm" #include "code\datums\mutations\actions.dm" #include "code\datums\mutations\antenna.dm" #include "code\datums\mutations\body.dm" From 625b35b8cbc034e9d9cb11544eab724ec7c89320 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Tue, 12 Nov 2019 23:59:31 -0700 Subject: [PATCH 12/21] Merge pull request #9755 from Ghommie/Ghommie-cit331 lentext to length, yet another 513 requirement. --- code/__HELPERS/text.dm | 1592 ++--- code/game/machinery/status_display.dm | 736 +-- code/game/objects/structures/musician.dm | 762 +-- code/modules/admin/topic.dm | 5982 +++++++++---------- code/modules/admin/verbs/modifyvariables.dm | 1286 ++-- tools/Redirector/textprocs.dm | 304 +- 6 files changed, 5331 insertions(+), 5331 deletions(-) diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index cb19779f..cd0d8678 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1,797 +1,797 @@ -/* - * Holds procs designed to help with filtering text - * Contains groups: - * SQL sanitization/formating - * Text sanitization - * Text searches - * Text modification - * Misc - */ - - -/* - * SQL sanitization - */ - -// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts. -/proc/sanitizeSQL(t) - return SSdbcore.Quote("[t]") - -/proc/format_table_name(table as text) - return CONFIG_GET(string/feedback_tableprefix) + table - -/* - * Text sanitization - */ - -//Simply removes < and > and limits the length of the message -/proc/strip_html_simple(t,limit=MAX_MESSAGE_LEN) - var/list/strip_chars = list("<",">") - t = copytext(t,1,limit) - for(var/char in strip_chars) - var/index = findtext(t, char) - while(index) - t = copytext(t, 1, index) + copytext(t, index+1) - index = findtext(t, char) - return t - -//Removes a few problematic characters -/proc/sanitize_simple(t,list/repl_chars = list("\n"="#","\t"="#")) - for(var/char in repl_chars) - var/index = findtext(t, char) - while(index) - t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index+1) - index = findtext(t, char, index+1) - return t - -/proc/sanitize_filename(t) - return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) - -//Runs byond's sanitization proc along-side sanitize_simple -/proc/sanitize(t,list/repl_chars = null) - return html_encode(sanitize_simple(t,repl_chars)) - -//Runs sanitize and strip_html_simple -//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode() -/proc/strip_html(t,limit=MAX_MESSAGE_LEN) - return copytext((sanitize(strip_html_simple(t))),1,limit) - -//Runs byond's sanitization proc along-side strip_html_simple -//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause -/proc/adminscrub(t,limit=MAX_MESSAGE_LEN) - return copytext((html_encode(strip_html_simple(t))),1,limit) - - -//Returns null if there is any bad text in the string -/proc/reject_bad_text(text, max_length=512) - if(length(text) > max_length) - return //message too long - var/non_whitespace = 0 - for(var/i=1, i<=length(text), i++) - switch(text2ascii(text,i)) - if(62,60,92,47) - return //rejects the text if it contains these bad characters: <, >, \ or / - if(127 to 255) - return //rejects weird letters like � - if(0 to 31) - return //more weird stuff - if(32) - continue //whitespace - else - non_whitespace = 1 - if(non_whitespace) - return text //only accepts the text if it has some non-spaces - -// Used to get a properly sanitized input, of max_length -// no_trim is self explanatory but it prevents the input from being trimed if you intend to parse newlines or whitespace. -/proc/stripped_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) - var/name = input(user, message, title, default) as text|null - if(no_trim) - return copytext(html_encode(name), 1, max_length) - else - return trim(html_encode(name), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <) - -// Used to get a properly sanitized multiline input, of max_length -/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) - var/name = input(user, message, title, default) as message|null - if(isnull(name)) // Return null if canceled. - return null - if(no_trim) - return copytext(html_encode(name), 1, max_length) - else - return trim(html_encode(name), max_length) - - -//Filters out undesirable characters from names -/proc/reject_bad_name(t_in, allow_numbers=1, max_length=MAX_NAME_LEN) - if(!t_in || length(t_in) > max_length) - return //Rejects the input if it is null or if it is longer then the max length allowed - - var/number_of_alphanumeric = 0 - var/last_char_group = 0 - var/t_out = "" - - for(var/i=1, i<=length(t_in), i++) - var/ascii_char = text2ascii(t_in,i) - switch(ascii_char) - // A .. Z - if(65 to 90) //Uppercase Letters - t_out += ascii2text(ascii_char) - number_of_alphanumeric++ - last_char_group = 4 - - // a .. z - if(97 to 122) //Lowercase Letters - if(last_char_group<2) - t_out += ascii2text(ascii_char-32) //Force uppercase first character - else - t_out += ascii2text(ascii_char) - number_of_alphanumeric++ - last_char_group = 4 - - // 0 .. 9 - if(48 to 57) //Numbers - if(!last_char_group) - continue //suppress at start of string - if(!allow_numbers) - continue - t_out += ascii2text(ascii_char) - number_of_alphanumeric++ - last_char_group = 3 - - // ' - . - if(39,45,46) //Common name punctuation - if(!last_char_group) - continue - t_out += ascii2text(ascii_char) - last_char_group = 2 - - // ~ | @ : # $ % & * + - if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) - if(!last_char_group) - continue //suppress at start of string - if(!allow_numbers) - continue - t_out += ascii2text(ascii_char) - last_char_group = 2 - - //Space - if(32) - if(last_char_group <= 1) - continue //suppress double-spaces and spaces at start of string - t_out += ascii2text(ascii_char) - last_char_group = 1 - else - return - - if(number_of_alphanumeric < 2) - return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" - - if(last_char_group == 1) - t_out = copytext(t_out,1,length(t_out)) //removes the last character (in this case a space) - - for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai")) //prevents these common metagamey names - if(cmptext(t_out,bad_name)) - return //(not case sensitive) - - return t_out - -//html_encode helper proc that returns the smallest non null of two numbers -//or 0 if they're both null (needed because of findtext returning 0 when a value is not present) -/proc/non_zero_min(a, b) - if(!a) - return b - if(!b) - return a - return (a < b ? a : b) - -/* - * Text searches - */ - -//Checks the beginning of a string for a specified sub-string -//Returns the position of the substring or 0 if it was not found -/proc/dd_hasprefix(text, prefix) - var/start = 1 - var/end = length(prefix) + 1 - return findtext(text, prefix, start, end) - -//Checks the beginning of a string for a specified sub-string. This proc is case sensitive -//Returns the position of the substring or 0 if it was not found -/proc/dd_hasprefix_case(text, prefix) - var/start = 1 - var/end = length(prefix) + 1 - return findtextEx(text, prefix, start, end) - -//Checks the end of a string for a specified substring. -//Returns the position of the substring or 0 if it was not found -/proc/dd_hassuffix(text, suffix) - var/start = length(text) - length(suffix) - if(start) - return findtext(text, suffix, start, null) - return - -//Checks the end of a string for a specified substring. This proc is case sensitive -//Returns the position of the substring or 0 if it was not found -/proc/dd_hassuffix_case(text, suffix) - var/start = length(text) - length(suffix) - if(start) - return findtextEx(text, suffix, start, null) - -//Checks if any of a given list of needles is in the haystack -/proc/text_in_list(haystack, list/needle_list, start=1, end=0) - for(var/needle in needle_list) - if(findtext(haystack, needle, start, end)) - return 1 - return 0 - -//Like above, but case sensitive -/proc/text_in_list_case(haystack, list/needle_list, start=1, end=0) - for(var/needle in needle_list) - if(findtextEx(haystack, needle, start, end)) - return 1 - return 0 - -//Adds 'u' number of zeros ahead of the text 't' -/proc/add_zero(t, u) - while (length(t) < u) - t = "0[t]" - return t - -//Adds 'u' number of spaces ahead of the text 't' -/proc/add_lspace(t, u) - while(length(t) < u) - t = " [t]" - return t - -//Adds 'u' number of spaces behind the text 't' -/proc/add_tspace(t, u) - while(length(t) < u) - t = "[t] " - return t - -//Returns a string with reserved characters and spaces before the first letter removed -/proc/trim_left(text) - for (var/i = 1 to length(text)) - if (text2ascii(text, i) > 32) - return copytext(text, i) - return "" - -//Returns a string with reserved characters and spaces after the last letter removed -/proc/trim_right(text) - for (var/i = length(text), i > 0, i--) - if (text2ascii(text, i) > 32) - return copytext(text, 1, i + 1) - - return "" - -//Returns a string with reserved characters and spaces before the first word and after the last word removed. -/proc/trim(text, max_length) - if(max_length) - text = copytext(text, 1, max_length) - return trim_left(trim_right(text)) - -//Returns a string with the first element of the string capitalized. -/proc/capitalize(t as text) - return uppertext(copytext(t, 1, 2)) + copytext(t, 2) - -//Centers text by adding spaces to either side of the string. -/proc/dd_centertext(message, length) - var/new_message = message - var/size = length(message) - var/delta = length - size - if(size == length) - return new_message - if(size > length) - return copytext(new_message, 1, length + 1) - if(delta == 1) - return new_message + " " - if(delta % 2) - new_message = " " + new_message - delta-- - var/spaces = add_lspace("",delta/2-1) - return spaces + new_message + spaces - -//Limits the length of the text. Note: MAX_MESSAGE_LEN and MAX_NAME_LEN are widely used for this purpose -/proc/dd_limittext(message, length) - var/size = length(message) - if(size <= length) - return message - return copytext(message, 1, length + 1) - - -/proc/stringmerge(text,compare,replace = "*") -//This proc fills in all spaces with the "replace" var (* by default) with whatever -//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(length(text) != length(compare)) - return 0 - 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 -//(no way to know what it was supposed to be) - if(a != b) - if(a == replace) //if A is the replacement char - newtext = copytext(newtext,1,i) + b + copytext(newtext, i+1) - else if(b == replace) //if B is the replacement char - newtext = copytext(newtext,1,i) + a + copytext(newtext, i+1) - else //The lists disagree, Uh-oh! - return 0 - return newtext - -/proc/stringpercent(text,character = "*") -//This proc returns the number of chars of the string that is the character -//This is used for detective work to determine fingerprint completion. - if(!text || !character) - return 0 - var/count = 0 - for(var/i = 1, i <= length(text), i++) - var/a = copytext(text,i,i+1) - if(a == character) - count++ - return count - -/proc/reverse_text(text = "") - var/new_text = "" - for(var/i = length(text); i > 0; i--) - new_text += copytext(text, i, i+1) - return new_text - -GLOBAL_LIST_INIT(zero_character_only, list("0")) -GLOBAL_LIST_INIT(hex_characters, list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f")) -GLOBAL_LIST_INIT(alphabet, 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")) -GLOBAL_LIST_INIT(binary, list("0","1")) -/proc/random_string(length, list/characters) - . = "" - for(var/i=1, i<=length, i++) - . += pick(characters) - -/proc/repeat_string(times, string="") - . = "" - for(var/i=1, i<=times, i++) - . += string - -/proc/random_short_color() - return random_string(3, GLOB.hex_characters) - -/proc/random_color() - return random_string(6, GLOB.hex_characters) - -/proc/add_zero2(t, u) - var/temp1 - while (length(t) < u) - t = "0[t]" - temp1 = t - if (length(t) > u) - temp1 = copytext(t,2,u+1) - return temp1 - -//merges non-null characters (3rd argument) from "from" into "into". Returns result -//e.g. into = "Hello World" -// from = "Seeya______" -// returns"Seeya World" -//The returned text is always the same length as into -//This was coded to handle DNA gene-splicing. -/proc/merge_text(into, from, null_char="_") - . = "" - if(!istext(into)) - into = "" - if(!istext(from)) - from = "" - var/null_ascii = istext(null_char) ? text2ascii(null_char,1) : null_char - - var/previous = 0 - var/start = 1 - var/end = length(into) + 1 - - for(var/i=1, i") - t = replacetext(t, "))", "") - t = replacetext(t, regex("(-){3,}", "gm"), "


") - t = replacetext(t, regex("^\\((-){3,}\\)$", "gm"), "$1") - - // Parse lists - - var/list/tlist = splittext(t, "\n") - var/tlistlen = tlist.len - var/listlevel = -1 - var/singlespace = -1 // if 0, double spaces are used before asterisks, if 1, single are - for(var/i = 1, i <= tlistlen, i++) - var/line = tlist[i] - var/count_asterisk = length(replacetext(line, regex("\[^\\*\]+", "g"), "")) - if(count_asterisk % 2 == 1 && findtext(line, regex("^\\s*\\*", "g"))) // there is an extra asterisk in the beggining - - var/count_w = length(replacetext(line, regex("^( *)\\*.*$", "g"), "$1")) // whitespace before asterisk - line = replacetext(line, regex("^ *(\\*.*)$", "g"), "$1") - - if(singlespace == -1 && count_w == 2) - if(listlevel == 0) - singlespace = 0 - else - singlespace = 1 - - if(singlespace == 0) - count_w = count_w % 2 ? round(count_w / 2 + 0.25) : count_w / 2 - - line = replacetext(line, regex("\\*", ""), "
  • ") - while(listlevel < count_w) - line = "
      " + line - listlevel++ - while(listlevel > count_w) - line = "
    " + line - listlevel-- - - else while(listlevel >= 0) - line = "" + line - listlevel-- - - tlist[i] = line - // end for - - t = tlist[1] - for(var/i = 2, i <= tlistlen, i++) - t += "\n" + tlist[i] - - while(listlevel >= 0) - t += "" - listlevel-- - - else - t = replacetext(t, "((", "") - t = replacetext(t, "))", "") - - // Parse headers - - t = replacetext(t, regex("^#(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^##(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^###(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^#### ?(.+)$", "gm"), "
    $1
    ") - - // Parse most rules - - t = replacetext(t, regex("\\*(\[^\\*\]*)\\*", "g"), "$1") - t = replacetext(t, regex("_(\[^_\]*)_", "g"), "$1") - t = replacetext(t, "", "!") - t = replacetext(t, "
    ", "!") - t = replacetext(t, regex("\\!(\[^\\!\]+)\\!", "g"), "$1") - t = replacetext(t, regex("\\^(\[^\\^\]+)\\^", "g"), "$1") - t = replacetext(t, regex("\\|(\[^\\|\]+)\\|", "g"), "
    $1
    ") - t = replacetext(t, "!", "
    ") - - return t - -/proc/parsemarkdown_basic_step2(t) - if(length(t) <= 0) - return - - // Restore the single characters used - - t = replacetext(t, "$a", "!") - - // Redo the escaping - - t = replacetext(t, "$1", "\\") - t = replacetext(t, "$2", "**") - t = replacetext(t, "$3", "*") - t = replacetext(t, "$4", "__") - t = replacetext(t, "$5", "_") - t = replacetext(t, "$6", "^") - t = replacetext(t, "$7", "((") - t = replacetext(t, "$8", "))") - t = replacetext(t, "$9", "|") - t = replacetext(t, "$0", "%") - t = replacetext(t, "$-", "$") - - return t - -/proc/parsemarkdown_basic(t, limited=FALSE) - t = parsemarkdown_basic_step1(t, limited) - t = parsemarkdown_basic_step2(t) - return t - -/proc/parsemarkdown(t, mob/user=null, limited=FALSE) - if(length(t) <= 0) - return - - // Premanage whitespace - - t = replacetext(t, regex("\[^\\S\\r\\n \]", "g"), " ") - - t = parsemarkdown_basic_step1(t) - - t = replacetext(t, regex("%s(?:ign)?(?=\\s|$)", "igm"), user ? "[user.real_name]" : "") - t = replacetext(t, regex("%f(?:ield)?(?=\\s|$)", "igm"), "") - - t = parsemarkdown_basic_step2(t) - - // Manage whitespace - - t = replacetext(t, regex("(?:\\r\\n?|\\n)", "g"), "
    ") - - t = replacetext(t, " ", "  ") - - // Done - - return t - -#define string2charlist(string) (splittext(string, regex("(.)")) - splittext(string, "")) - -/proc/rot13(text = "") - var/list/textlist = string2charlist(text) - var/list/result = list() - for(var/c in textlist) - var/ca = text2ascii(c) - if(ca >= text2ascii("a") && ca <= text2ascii("m")) - ca += 13 - else if(ca >= text2ascii("n") && ca <= text2ascii("z")) - ca -= 13 - else if(ca >= text2ascii("A") && ca <= text2ascii("M")) - ca += 13 - else if(ca >= text2ascii("N") && ca <= text2ascii("Z")) - ca -= 13 - result += ascii2text(ca) - return jointext(result, "") - -//Takes a list of values, sanitizes it down for readability and character count, -//then exports it as a json file at data/npc_saves/[filename].json. -//As far as SS13 is concerned this is write only data. You can't change something -//in the json file and have it be reflected in the in game item/mob it came from. -//(That's what things like savefiles are for) Note that this list is not shuffled. -/proc/twitterize(list/proposed, filename, cullshort = 1, storemax = 1000) - if(!islist(proposed) || !filename || !CONFIG_GET(flag/log_twitter)) - return - - //Regular expressions are, as usual, absolute magic - var/regex/all_invalid_symbols = new("\[^ -~]+") - - var/list/accepted = list() - for(var/string in proposed) - if(findtext(string,GLOB.is_website) || findtext(string,GLOB.is_email) || findtext(string,all_invalid_symbols) || !findtext(string,GLOB.is_alphanumeric)) - continue - var/buffer = "" - var/early_culling = TRUE - for(var/pos = 1, pos <= length(string), pos++) - var/let = copytext(string, pos, (pos + 1) % length(string)) - if(early_culling && !findtext(let,GLOB.is_alphanumeric)) - continue - early_culling = FALSE - buffer += let - if(!findtext(buffer,GLOB.is_alphanumeric)) - continue - var/punctbuffer = "" - var/cutoff = length(buffer) - for(var/pos = length(buffer), pos >= 0, pos--) - var/let = copytext(buffer, pos, (pos + 1) % length(buffer)) - if(findtext(let,GLOB.is_alphanumeric)) - break - if(findtext(let,GLOB.is_punctuation)) - punctbuffer = let + punctbuffer //Note this isn't the same thing as using += - cutoff = pos - if(punctbuffer) //We clip down excessive punctuation to get the letter count lower and reduce repeats. It's not perfect but it helps. - var/exclaim = FALSE - var/question = FALSE - var/periods = 0 - for(var/pos = length(punctbuffer), pos >= 0, pos--) - var/punct = copytext(punctbuffer, pos, (pos + 1) % length(punctbuffer)) - if(!exclaim && findtext(punct,"!")) - exclaim = TRUE - if(!question && findtext(punct,"?")) - question = TRUE - if(!exclaim && !question && findtext(punct,".")) - periods += 1 - if(exclaim) - if(question) - punctbuffer = "?!" - else - punctbuffer = "!" - else if(question) - punctbuffer = "?" - else if(periods) - if(periods > 1) - punctbuffer = "..." - else - punctbuffer = "" //Grammer nazis be damned - buffer = copytext(buffer, 1, cutoff) + punctbuffer - if(!findtext(buffer,GLOB.is_alphanumeric)) - continue - if(!buffer || length(buffer) > 280 || length(buffer) <= cullshort || buffer in accepted) - continue - - accepted += buffer - - var/log = file("data/npc_saves/[filename].json") //If this line ever shows up as changed in a PR be very careful you aren't being memed on - var/list/oldjson = list() - var/list/oldentries = list() - if(fexists(log)) - oldjson = json_decode(file2text(log)) - oldentries = oldjson["data"] - if(!isemptylist(oldentries)) - for(var/string in accepted) - for(var/old in oldentries) - if(string == old) - oldentries.Remove(old) //Line's position in line is "refreshed" until it falls off the in game radar - break - - var/list/finalized = list() - finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling - listclearnulls(finalized) - if(!isemptylist(finalized) && length(finalized) > storemax) - finalized.Cut(storemax + 1) - fdel(log) - - var/list/tosend = list() - tosend["data"] = finalized - WRITE_FILE(log, json_encode(tosend)) - -//Used for applying byonds text macros to strings that are loaded at runtime -/proc/apply_text_macros(string) - var/next_backslash = findtext(string, "\\") - if(!next_backslash) - return string - - var/leng = length(string) - - var/next_space = findtext(string, " ", next_backslash + 1) - if(!next_space) - next_space = leng - next_backslash - - if(!next_space) //trailing bs - return string - - var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash) - var/macro = lowertext(copytext(string, next_backslash + 1, next_space)) - var/rest = next_backslash > leng ? "" : copytext(string, next_space + 1) - - //See https://secure.byond.com/docs/ref/info.html#/DM/text/macros - switch(macro) - //prefixes/agnostic - if("the") - rest = text("\the []", rest) - if("a") - rest = text("\a []", rest) - if("an") - rest = text("\an []", rest) - if("proper") - rest = text("\proper []", rest) - if("improper") - rest = text("\improper []", rest) - if("roman") - rest = text("\roman []", rest) - //postfixes - if("th") - base = text("[]\th", rest) - if("s") - base = text("[]\s", rest) - if("he") - base = text("[]\he", rest) - if("she") - base = text("[]\she", rest) - if("his") - base = text("[]\his", rest) - if("himself") - base = text("[]\himself", rest) - if("herself") - base = text("[]\herself", rest) - if("hers") - base = text("[]\hers", rest) - - . = base - if(rest) - . += .(rest) - -//Replacement for the \th macro when you want the whole word output as text (first instead of 1st) -/proc/thtotext(number) - if(!isnum(number)) - return - switch(number) - if(1) - return "first" - if(2) - return "second" - if(3) - return "third" - if(4) - return "fourth" - if(5) - return "fifth" - if(6) - return "sixth" - if(7) - return "seventh" - if(8) - return "eighth" - if(9) - return "ninth" - if(10) - return "tenth" - if(11) - return "eleventh" - if(12) - return "twelfth" - else - return "[number]\th" - -/proc/unintelligize(message) - var/prefix=copytext(message,1,2) - if(prefix == ";") - message = copytext(message,2) - else if(prefix in list(":","#")) - prefix += copytext(message,2,3) - message = copytext(message,3) - else - prefix="" - - var/list/words = splittext(message," ") - var/list/rearranged = list() - for(var/i=1;i<=words.len;i++) - var/cword = pick(words) - words.Remove(cword) - var/suffix = copytext(cword,length(cword)-1,length(cword)) - while(length(cword)>0 && suffix in list(".",",",";","!",":","?")) - cword = copytext(cword,1 ,length(cword)-1) - suffix = copytext(cword,length(cword)-1,length(cword) ) - if(length(cword)) - rearranged += cword - message = "[prefix][jointext(rearranged," ")]" - . = message - -#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) +/* + * Holds procs designed to help with filtering text + * Contains groups: + * SQL sanitization/formating + * Text sanitization + * Text searches + * Text modification + * Misc + */ + + +/* + * SQL sanitization + */ + +// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts. +/proc/sanitizeSQL(t) + return SSdbcore.Quote("[t]") + +/proc/format_table_name(table as text) + return CONFIG_GET(string/feedback_tableprefix) + table + +/* + * Text sanitization + */ + +//Simply removes < and > and limits the length of the message +/proc/strip_html_simple(t,limit=MAX_MESSAGE_LEN) + var/list/strip_chars = list("<",">") + t = copytext(t,1,limit) + for(var/char in strip_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + copytext(t, index+1) + index = findtext(t, char) + return t + +//Removes a few problematic characters +/proc/sanitize_simple(t,list/repl_chars = list("\n"="#","\t"="#")) + for(var/char in repl_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index+1) + index = findtext(t, char, index+1) + return t + +/proc/sanitize_filename(t) + return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) + +//Runs byond's sanitization proc along-side sanitize_simple +/proc/sanitize(t,list/repl_chars = null) + return html_encode(sanitize_simple(t,repl_chars)) + +//Runs sanitize and strip_html_simple +//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode() +/proc/strip_html(t,limit=MAX_MESSAGE_LEN) + return copytext((sanitize(strip_html_simple(t))),1,limit) + +//Runs byond's sanitization proc along-side strip_html_simple +//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause +/proc/adminscrub(t,limit=MAX_MESSAGE_LEN) + return copytext((html_encode(strip_html_simple(t))),1,limit) + + +//Returns null if there is any bad text in the string +/proc/reject_bad_text(text, max_length=512) + if(length(text) > max_length) + return //message too long + var/non_whitespace = 0 + for(var/i=1, i<=length(text), i++) + switch(text2ascii(text,i)) + if(62,60,92,47) + return //rejects the text if it contains these bad characters: <, >, \ or / + if(127 to 255) + return //rejects weird letters like � + if(0 to 31) + return //more weird stuff + if(32) + continue //whitespace + else + non_whitespace = 1 + if(non_whitespace) + return text //only accepts the text if it has some non-spaces + +// Used to get a properly sanitized input, of max_length +// no_trim is self explanatory but it prevents the input from being trimed if you intend to parse newlines or whitespace. +/proc/stripped_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) + var/name = input(user, message, title, default) as text|null + if(no_trim) + return copytext(html_encode(name), 1, max_length) + else + return trim(html_encode(name), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <) + +// Used to get a properly sanitized multiline input, of max_length +/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) + var/name = input(user, message, title, default) as message|null + if(isnull(name)) // Return null if canceled. + return null + if(no_trim) + return copytext(html_encode(name), 1, max_length) + else + return trim(html_encode(name), max_length) + + +//Filters out undesirable characters from names +/proc/reject_bad_name(t_in, allow_numbers=1, max_length=MAX_NAME_LEN) + if(!t_in || length(t_in) > max_length) + return //Rejects the input if it is null or if it is longer then the max length allowed + + var/number_of_alphanumeric = 0 + var/last_char_group = 0 + var/t_out = "" + + for(var/i=1, i<=length(t_in), i++) + var/ascii_char = text2ascii(t_in,i) + switch(ascii_char) + // A .. Z + if(65 to 90) //Uppercase Letters + t_out += ascii2text(ascii_char) + number_of_alphanumeric++ + last_char_group = 4 + + // a .. z + if(97 to 122) //Lowercase Letters + if(last_char_group<2) + t_out += ascii2text(ascii_char-32) //Force uppercase first character + else + t_out += ascii2text(ascii_char) + number_of_alphanumeric++ + last_char_group = 4 + + // 0 .. 9 + if(48 to 57) //Numbers + if(!last_char_group) + continue //suppress at start of string + if(!allow_numbers) + continue + t_out += ascii2text(ascii_char) + number_of_alphanumeric++ + last_char_group = 3 + + // ' - . + if(39,45,46) //Common name punctuation + if(!last_char_group) + continue + t_out += ascii2text(ascii_char) + last_char_group = 2 + + // ~ | @ : # $ % & * + + if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) + if(!last_char_group) + continue //suppress at start of string + if(!allow_numbers) + continue + t_out += ascii2text(ascii_char) + last_char_group = 2 + + //Space + if(32) + if(last_char_group <= 1) + continue //suppress double-spaces and spaces at start of string + t_out += ascii2text(ascii_char) + last_char_group = 1 + else + return + + if(number_of_alphanumeric < 2) + return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" + + if(last_char_group == 1) + t_out = copytext(t_out,1,length(t_out)) //removes the last character (in this case a space) + + for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai")) //prevents these common metagamey names + if(cmptext(t_out,bad_name)) + return //(not case sensitive) + + return t_out + +//html_encode helper proc that returns the smallest non null of two numbers +//or 0 if they're both null (needed because of findtext returning 0 when a value is not present) +/proc/non_zero_min(a, b) + if(!a) + return b + if(!b) + return a + return (a < b ? a : b) + +/* + * Text searches + */ + +//Checks the beginning of a string for a specified sub-string +//Returns the position of the substring or 0 if it was not found +/proc/dd_hasprefix(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtext(text, prefix, start, end) + +//Checks the beginning of a string for a specified sub-string. This proc is case sensitive +//Returns the position of the substring or 0 if it was not found +/proc/dd_hasprefix_case(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtextEx(text, prefix, start, end) + +//Checks the end of a string for a specified substring. +//Returns the position of the substring or 0 if it was not found +/proc/dd_hassuffix(text, suffix) + var/start = length(text) - length(suffix) + if(start) + return findtext(text, suffix, start, null) + return + +//Checks the end of a string for a specified substring. This proc is case sensitive +//Returns the position of the substring or 0 if it was not found +/proc/dd_hassuffix_case(text, suffix) + var/start = length(text) - length(suffix) + if(start) + return findtextEx(text, suffix, start, null) + +//Checks if any of a given list of needles is in the haystack +/proc/text_in_list(haystack, list/needle_list, start=1, end=0) + for(var/needle in needle_list) + if(findtext(haystack, needle, start, end)) + return 1 + return 0 + +//Like above, but case sensitive +/proc/text_in_list_case(haystack, list/needle_list, start=1, end=0) + for(var/needle in needle_list) + if(findtextEx(haystack, needle, start, end)) + return 1 + return 0 + +//Adds 'u' number of zeros ahead of the text 't' +/proc/add_zero(t, u) + while (length(t) < u) + t = "0[t]" + return t + +//Adds 'u' number of spaces ahead of the text 't' +/proc/add_lspace(t, u) + while(length(t) < u) + t = " [t]" + return t + +//Adds 'u' number of spaces behind the text 't' +/proc/add_tspace(t, u) + while(length(t) < u) + t = "[t] " + return t + +//Returns a string with reserved characters and spaces before the first letter removed +/proc/trim_left(text) + for (var/i = 1 to length(text)) + if (text2ascii(text, i) > 32) + return copytext(text, i) + return "" + +//Returns a string with reserved characters and spaces after the last letter removed +/proc/trim_right(text) + for (var/i = length(text), i > 0, i--) + if (text2ascii(text, i) > 32) + return copytext(text, 1, i + 1) + + return "" + +//Returns a string with reserved characters and spaces before the first word and after the last word removed. +/proc/trim(text, max_length) + if(max_length) + text = copytext(text, 1, max_length) + return trim_left(trim_right(text)) + +//Returns a string with the first element of the string capitalized. +/proc/capitalize(t as text) + return uppertext(copytext(t, 1, 2)) + copytext(t, 2) + +//Centers text by adding spaces to either side of the string. +/proc/dd_centertext(message, length) + var/new_message = message + var/size = length(message) + var/delta = length - size + if(size == length) + return new_message + if(size > length) + return copytext(new_message, 1, length + 1) + if(delta == 1) + return new_message + " " + if(delta % 2) + new_message = " " + new_message + delta-- + var/spaces = add_lspace("",delta/2-1) + return spaces + new_message + spaces + +//Limits the length of the text. Note: MAX_MESSAGE_LEN and MAX_NAME_LEN are widely used for this purpose +/proc/dd_limittext(message, length) + var/size = length(message) + if(size <= length) + return message + return copytext(message, 1, length + 1) + + +/proc/stringmerge(text,compare,replace = "*") +//This proc fills in all spaces with the "replace" var (* by default) with whatever +//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(length(text) != length(compare)) + return 0 + 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 +//(no way to know what it was supposed to be) + if(a != b) + if(a == replace) //if A is the replacement char + newtext = copytext(newtext,1,i) + b + copytext(newtext, i+1) + else if(b == replace) //if B is the replacement char + newtext = copytext(newtext,1,i) + a + copytext(newtext, i+1) + else //The lists disagree, Uh-oh! + return 0 + return newtext + +/proc/stringpercent(text,character = "*") +//This proc returns the number of chars of the string that is the character +//This is used for detective work to determine fingerprint completion. + if(!text || !character) + return 0 + var/count = 0 + for(var/i = 1, i <= length(text), i++) + var/a = copytext(text,i,i+1) + if(a == character) + count++ + return count + +/proc/reverse_text(text = "") + var/new_text = "" + for(var/i = length(text); i > 0; i--) + new_text += copytext(text, i, i+1) + return new_text + +GLOBAL_LIST_INIT(zero_character_only, list("0")) +GLOBAL_LIST_INIT(hex_characters, list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f")) +GLOBAL_LIST_INIT(alphabet, 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")) +GLOBAL_LIST_INIT(binary, list("0","1")) +/proc/random_string(length, list/characters) + . = "" + for(var/i=1, i<=length, i++) + . += pick(characters) + +/proc/repeat_string(times, string="") + . = "" + for(var/i=1, i<=times, i++) + . += string + +/proc/random_short_color() + return random_string(3, GLOB.hex_characters) + +/proc/random_color() + return random_string(6, GLOB.hex_characters) + +/proc/add_zero2(t, u) + var/temp1 + while (length(t) < u) + t = "0[t]" + temp1 = t + if (length(t) > u) + temp1 = copytext(t,2,u+1) + return temp1 + +//merges non-null characters (3rd argument) from "from" into "into". Returns result +//e.g. into = "Hello World" +// from = "Seeya______" +// returns"Seeya World" +//The returned text is always the same length as into +//This was coded to handle DNA gene-splicing. +/proc/merge_text(into, from, null_char="_") + . = "" + if(!istext(into)) + into = "" + if(!istext(from)) + from = "" + var/null_ascii = istext(null_char) ? text2ascii(null_char,1) : null_char + + var/previous = 0 + var/start = 1 + var/end = length(into) + 1 + + for(var/i=1, i") + t = replacetext(t, "))", "") + t = replacetext(t, regex("(-){3,}", "gm"), "
    ") + t = replacetext(t, regex("^\\((-){3,}\\)$", "gm"), "$1") + + // Parse lists + + var/list/tlist = splittext(t, "\n") + var/tlistlen = tlist.len + var/listlevel = -1 + var/singlespace = -1 // if 0, double spaces are used before asterisks, if 1, single are + for(var/i = 1, i <= tlistlen, i++) + var/line = tlist[i] + var/count_asterisk = length(replacetext(line, regex("\[^\\*\]+", "g"), "")) + if(count_asterisk % 2 == 1 && findtext(line, regex("^\\s*\\*", "g"))) // there is an extra asterisk in the beggining + + var/count_w = length(replacetext(line, regex("^( *)\\*.*$", "g"), "$1")) // whitespace before asterisk + line = replacetext(line, regex("^ *(\\*.*)$", "g"), "$1") + + if(singlespace == -1 && count_w == 2) + if(listlevel == 0) + singlespace = 0 + else + singlespace = 1 + + if(singlespace == 0) + count_w = count_w % 2 ? round(count_w / 2 + 0.25) : count_w / 2 + + line = replacetext(line, regex("\\*", ""), "
  • ") + while(listlevel < count_w) + line = "
      " + line + listlevel++ + while(listlevel > count_w) + line = "
    " + line + listlevel-- + + else while(listlevel >= 0) + line = "" + line + listlevel-- + + tlist[i] = line + // end for + + t = tlist[1] + for(var/i = 2, i <= tlistlen, i++) + t += "\n" + tlist[i] + + while(listlevel >= 0) + t += "" + listlevel-- + + else + t = replacetext(t, "((", "") + t = replacetext(t, "))", "") + + // Parse headers + + t = replacetext(t, regex("^#(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^##(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^###(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^#### ?(.+)$", "gm"), "
    $1
    ") + + // Parse most rules + + t = replacetext(t, regex("\\*(\[^\\*\]*)\\*", "g"), "$1") + t = replacetext(t, regex("_(\[^_\]*)_", "g"), "$1") + t = replacetext(t, "", "!") + t = replacetext(t, "
    ", "!") + t = replacetext(t, regex("\\!(\[^\\!\]+)\\!", "g"), "$1") + t = replacetext(t, regex("\\^(\[^\\^\]+)\\^", "g"), "$1") + t = replacetext(t, regex("\\|(\[^\\|\]+)\\|", "g"), "
    $1
    ") + t = replacetext(t, "!", "
    ") + + return t + +/proc/parsemarkdown_basic_step2(t) + if(length(t) <= 0) + return + + // Restore the single characters used + + t = replacetext(t, "$a", "!") + + // Redo the escaping + + t = replacetext(t, "$1", "\\") + t = replacetext(t, "$2", "**") + t = replacetext(t, "$3", "*") + t = replacetext(t, "$4", "__") + t = replacetext(t, "$5", "_") + t = replacetext(t, "$6", "^") + t = replacetext(t, "$7", "((") + t = replacetext(t, "$8", "))") + t = replacetext(t, "$9", "|") + t = replacetext(t, "$0", "%") + t = replacetext(t, "$-", "$") + + return t + +/proc/parsemarkdown_basic(t, limited=FALSE) + t = parsemarkdown_basic_step1(t, limited) + t = parsemarkdown_basic_step2(t) + return t + +/proc/parsemarkdown(t, mob/user=null, limited=FALSE) + if(length(t) <= 0) + return + + // Premanage whitespace + + t = replacetext(t, regex("\[^\\S\\r\\n \]", "g"), " ") + + t = parsemarkdown_basic_step1(t) + + t = replacetext(t, regex("%s(?:ign)?(?=\\s|$)", "igm"), user ? "[user.real_name]" : "") + t = replacetext(t, regex("%f(?:ield)?(?=\\s|$)", "igm"), "") + + t = parsemarkdown_basic_step2(t) + + // Manage whitespace + + t = replacetext(t, regex("(?:\\r\\n?|\\n)", "g"), "
    ") + + t = replacetext(t, " ", "  ") + + // Done + + return t + +#define string2charlist(string) (splittext(string, regex("(.)")) - splittext(string, "")) + +/proc/rot13(text = "") + var/list/textlist = string2charlist(text) + var/list/result = list() + for(var/c in textlist) + var/ca = text2ascii(c) + if(ca >= text2ascii("a") && ca <= text2ascii("m")) + ca += 13 + else if(ca >= text2ascii("n") && ca <= text2ascii("z")) + ca -= 13 + else if(ca >= text2ascii("A") && ca <= text2ascii("M")) + ca += 13 + else if(ca >= text2ascii("N") && ca <= text2ascii("Z")) + ca -= 13 + result += ascii2text(ca) + return jointext(result, "") + +//Takes a list of values, sanitizes it down for readability and character count, +//then exports it as a json file at data/npc_saves/[filename].json. +//As far as SS13 is concerned this is write only data. You can't change something +//in the json file and have it be reflected in the in game item/mob it came from. +//(That's what things like savefiles are for) Note that this list is not shuffled. +/proc/twitterize(list/proposed, filename, cullshort = 1, storemax = 1000) + if(!islist(proposed) || !filename || !CONFIG_GET(flag/log_twitter)) + return + + //Regular expressions are, as usual, absolute magic + var/regex/all_invalid_symbols = new("\[^ -~]+") + + var/list/accepted = list() + for(var/string in proposed) + if(findtext(string,GLOB.is_website) || findtext(string,GLOB.is_email) || findtext(string,all_invalid_symbols) || !findtext(string,GLOB.is_alphanumeric)) + continue + var/buffer = "" + var/early_culling = TRUE + for(var/pos = 1, pos <= length(string), pos++) + var/let = copytext(string, pos, (pos + 1) % length(string)) + if(early_culling && !findtext(let,GLOB.is_alphanumeric)) + continue + early_culling = FALSE + buffer += let + if(!findtext(buffer,GLOB.is_alphanumeric)) + continue + var/punctbuffer = "" + var/cutoff = length(buffer) + for(var/pos = length(buffer), pos >= 0, pos--) + var/let = copytext(buffer, pos, (pos + 1) % length(buffer)) + if(findtext(let,GLOB.is_alphanumeric)) + break + if(findtext(let,GLOB.is_punctuation)) + punctbuffer = let + punctbuffer //Note this isn't the same thing as using += + cutoff = pos + if(punctbuffer) //We clip down excessive punctuation to get the letter count lower and reduce repeats. It's not perfect but it helps. + var/exclaim = FALSE + var/question = FALSE + var/periods = 0 + for(var/pos = length(punctbuffer), pos >= 0, pos--) + var/punct = copytext(punctbuffer, pos, (pos + 1) % length(punctbuffer)) + if(!exclaim && findtext(punct,"!")) + exclaim = TRUE + if(!question && findtext(punct,"?")) + question = TRUE + if(!exclaim && !question && findtext(punct,".")) + periods += 1 + if(exclaim) + if(question) + punctbuffer = "?!" + else + punctbuffer = "!" + else if(question) + punctbuffer = "?" + else if(periods) + if(periods > 1) + punctbuffer = "..." + else + punctbuffer = "" //Grammer nazis be damned + buffer = copytext(buffer, 1, cutoff) + punctbuffer + if(!findtext(buffer,GLOB.is_alphanumeric)) + continue + if(!buffer || length(buffer) > 280 || length(buffer) <= cullshort || buffer in accepted) + continue + + accepted += buffer + + var/log = file("data/npc_saves/[filename].json") //If this line ever shows up as changed in a PR be very careful you aren't being memed on + var/list/oldjson = list() + var/list/oldentries = list() + if(fexists(log)) + oldjson = json_decode(file2text(log)) + oldentries = oldjson["data"] + if(!isemptylist(oldentries)) + for(var/string in accepted) + for(var/old in oldentries) + if(string == old) + oldentries.Remove(old) //Line's position in line is "refreshed" until it falls off the in game radar + break + + var/list/finalized = list() + finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling + listclearnulls(finalized) + if(!isemptylist(finalized) && length(finalized) > storemax) + finalized.Cut(storemax + 1) + fdel(log) + + var/list/tosend = list() + tosend["data"] = finalized + WRITE_FILE(log, json_encode(tosend)) + +//Used for applying byonds text macros to strings that are loaded at runtime +/proc/apply_text_macros(string) + var/next_backslash = findtext(string, "\\") + if(!next_backslash) + return string + + var/leng = length(string) + + var/next_space = findtext(string, " ", next_backslash + 1) + if(!next_space) + next_space = leng - next_backslash + + if(!next_space) //trailing bs + return string + + var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash) + var/macro = lowertext(copytext(string, next_backslash + 1, next_space)) + var/rest = next_backslash > leng ? "" : copytext(string, next_space + 1) + + //See https://secure.byond.com/docs/ref/info.html#/DM/text/macros + switch(macro) + //prefixes/agnostic + if("the") + rest = text("\the []", rest) + if("a") + rest = text("\a []", rest) + if("an") + rest = text("\an []", rest) + if("proper") + rest = text("\proper []", rest) + if("improper") + rest = text("\improper []", rest) + if("roman") + rest = text("\roman []", rest) + //postfixes + if("th") + base = text("[]\th", rest) + if("s") + base = text("[]\s", rest) + if("he") + base = text("[]\he", rest) + if("she") + base = text("[]\she", rest) + if("his") + base = text("[]\his", rest) + if("himself") + base = text("[]\himself", rest) + if("herself") + base = text("[]\herself", rest) + if("hers") + base = text("[]\hers", rest) + + . = base + if(rest) + . += .(rest) + +//Replacement for the \th macro when you want the whole word output as text (first instead of 1st) +/proc/thtotext(number) + if(!isnum(number)) + return + switch(number) + if(1) + return "first" + if(2) + return "second" + if(3) + return "third" + if(4) + return "fourth" + if(5) + return "fifth" + if(6) + return "sixth" + if(7) + return "seventh" + if(8) + return "eighth" + if(9) + return "ninth" + if(10) + return "tenth" + if(11) + return "eleventh" + if(12) + return "twelfth" + else + return "[number]\th" + +/proc/unintelligize(message) + var/prefix=copytext(message,1,2) + if(prefix == ";") + message = copytext(message,2) + else if(prefix in list(":","#")) + prefix += copytext(message,2,3) + message = copytext(message,3) + else + prefix="" + + var/list/words = splittext(message," ") + var/list/rearranged = list() + for(var/i=1;i<=words.len;i++) + var/cword = pick(words) + words.Remove(cword) + var/suffix = copytext(cword,length(cword)-1,length(cword)) + while(length(cword)>0 && suffix in list(".",",",";","!",":","?")) + cword = copytext(cword,1 ,length(cword)-1) + suffix = copytext(cword,length(cword)-1,length(cword) ) + if(length(cword)) + rearranged += cword + message = "[prefix][jointext(rearranged," ")]" + . = message + +#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) #define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) \ No newline at end of file diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index a722fcaf..af5f50f2 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -1,368 +1,368 @@ -// Status display -// (formerly Countdown timer display) - -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Arial Black" -#define SCROLL_SPEED 2 - -#define SD_BLANK 0 // 0 = Blank -#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer -#define SD_MESSAGE 2 // 2 = Arbitrary message(s) -#define SD_PICTURE 3 // 3 = alert picture - -#define SD_AI_EMOTE 1 // 1 = AI emoticon -#define SD_AI_BSOD 2 // 2 = Blue screen of death - -/// Status display which can show images and scrolling text. -/obj/machinery/status_display - name = "status display" - desc = null - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - - maptext_height = 26 - maptext_width = 32 - - var/message1 = "" // message line 1 - var/message2 = "" // message line 2 - var/index1 // display index for scrolling messages or 0 if non-scrolling - var/index2 - -/// Immediately blank the display. -/obj/machinery/status_display/proc/remove_display() - cut_overlays() - if(maptext) - maptext = "" - -/// Immediately change the display to the given picture. -/obj/machinery/status_display/proc/set_picture(state) - remove_display() - add_overlay(state) - -/// Immediately change the display to the given two lines. -/obj/machinery/status_display/proc/update_display(line1, line2) - var/new_text = {"
    [line1]
    [line2]
    "} - if(maptext != new_text) - maptext = new_text - -/// Prepare the display to marquee the given two lines. -/// -/// Call with no arguments to disable. -/obj/machinery/status_display/proc/set_message(m1, m2) - if(m1) - index1 = (length(m1) > CHARS_PER_LINE) - message1 = m1 - else - message1 = "" - index1 = 0 - - if(m2) - index2 = (length(m2) > CHARS_PER_LINE) - message2 = m2 - else - message2 = "" - index2 = 0 - -// Timed process - performs default marquee action if so needed. -/obj/machinery/status_display/process() - if(stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - var/line1 = message1 - if(index1) - line1 = copytext("[message1]|[message1]", index1, index1+CHARS_PER_LINE) - var/message1_len = length(message1) - index1 += SCROLL_SPEED - if(index1 > message1_len) - index1 -= message1_len - - var/line2 = message2 - if(index2) - line2 = copytext("[message2]|[message2]", index2, index2+CHARS_PER_LINE) - var/message2_len = length(message2) - index2 += SCROLL_SPEED - if(index2 > message2_len) - index2 -= message2_len - - update_display(line1, line2) - if (!index1 && !index2) - // No marquee, no processing. - return PROCESS_KILL - -/// Update the display and, if necessary, re-enable processing. -/obj/machinery/status_display/proc/update() - if (process() != PROCESS_KILL) - START_PROCESSING(SSmachines, src) - -/obj/machinery/status_display/power_change() - . = ..() - update() - -/obj/machinery/status_display/emp_act(severity) - . = ..() - if(stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF) - return - set_picture("ai_bsod") - -/obj/machinery/status_display/examine(mob/user) - . = ..() - if (message1 || message2) - . += "The display says:" - if (message1) - . += "\t[html_encode(message1)]" - if (message2) - . += "\t[html_encode(message2)]" - -// Helper procs for child display types. -/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle) - if(!shuttle) - // the shuttle is missing - no processing - update_display("shutl?","") - return PROCESS_KILL - else if(shuttle.timer) - var/line1 = "-[shuttle.getModeStr()]-" - var/line2 = shuttle.getTimerStr() - - if(length(line2) > CHARS_PER_LINE) - line2 = "error" - update_display(line1, line2) - else - // don't kill processing, the timer might turn back on - remove_display() - -/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle) - if (shuttle) - var/modestr = shuttle.getModeStr() - if (modestr) - if (shuttle.timer) - modestr = "
    \t[modestr]: [shuttle.getTimerStr()]" - else - modestr = "
    \t[modestr]" - return "The display says:
    \t[shuttle.name][modestr]" - else - return "The display says:
    \tShuttle missing!" - - -/// Evac display which shows shuttle timer or message set by Command. -/obj/machinery/status_display/evac - var/frequency = FREQ_STATUS_DISPLAYS - var/mode = SD_EMERGENCY - var/friendc = FALSE // track if Friend Computer mode - var/last_picture // For when Friend Computer mode is undone - -/obj/machinery/status_display/evac/Initialize() - . = ..() - // register for radio system - SSradio.add_object(src, frequency) - -/obj/machinery/status_display/evac/Destroy() - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/status_display/evac/process() - if(stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - if(friendc) //Makes all status displays except supply shuttle timer display the eye -- Urist - set_picture("ai_friend") - return PROCESS_KILL - - switch(mode) - if(SD_BLANK) - remove_display() - return PROCESS_KILL - - if(SD_EMERGENCY) - return display_shuttle_status(SSshuttle.emergency) - - if(SD_MESSAGE) - return ..() - - if(SD_PICTURE) - set_picture(last_picture) - return PROCESS_KILL - -/obj/machinery/status_display/evac/examine(mob/user) - . = ..() - if(mode == SD_EMERGENCY) - . += examine_shuttle(user, SSshuttle.emergency) - else if(!message1 && !message2) - . += "The display is blank." - -/obj/machinery/status_display/evac/receive_signal(datum/signal/signal) - switch(signal.data["command"]) - if("blank") - mode = SD_BLANK - set_message(null, null) - if("shuttle") - mode = SD_EMERGENCY - set_message(null, null) - if("message") - mode = SD_MESSAGE - set_message(signal.data["msg1"], signal.data["msg2"]) - if("alert") - mode = SD_PICTURE - last_picture = signal.data["picture_state"] - set_picture(last_picture) - if("friendcomputer") - friendc = !friendc - update() - - -/// Supply display which shows the status of the supply shuttle. -/obj/machinery/status_display/supply - name = "supply display" - -/obj/machinery/status_display/supply/process() - if(stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - var/line1 - var/line2 - if(!SSshuttle.supply) - // Might be missing in our first update on initialize before shuttles - // have loaded. Cross our fingers that it will soon return. - line1 = "CARGO" - line2 = "shutl?" - else if(SSshuttle.supply.mode == SHUTTLE_IDLE) - if(is_station_level(SSshuttle.supply.z)) - line1 = "CARGO" - line2 = "Docked" - else - line1 = "CARGO" - line2 = SSshuttle.supply.getTimerStr() - if(length(line2) > CHARS_PER_LINE) - line2 = "Error" - update_display(line1, line2) - -/obj/machinery/status_display/supply/examine(mob/user) - . = ..() - var/obj/docking_port/mobile/shuttle = SSshuttle.supply - var/shuttleMsg = null - if (shuttle.mode == SHUTTLE_IDLE) - if (is_station_level(shuttle.z)) - shuttleMsg = "Docked" - else - shuttleMsg = "[shuttle.getModeStr()]: [shuttle.getTimerStr()]" - if (shuttleMsg) - . += "The display says:
    \t[shuttleMsg]" - else - . += "The display is blank." - - -/// General-purpose shuttle status display. -/obj/machinery/status_display/shuttle - name = "shuttle display" - var/shuttle_id - -/obj/machinery/status_display/shuttle/process() - if(!shuttle_id || (stat & NOPOWER)) - // No power, no processing. - remove_display() - return PROCESS_KILL - - return display_shuttle_status(SSshuttle.getShuttle(shuttle_id)) - -/obj/machinery/status_display/shuttle/examine(mob/user) - . = ..() - if(shuttle_id) - . += examine_shuttle(user, SSshuttle.getShuttle(shuttle_id)) - else - . += "The display is blank." - -/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value) - . = ..() - if(!.) - return - switch(var_name) - if("shuttle_id") - update() - -/obj/machinery/status_display/shuttle/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) - if (port && (shuttle_id == initial(shuttle_id) || override)) - shuttle_id = port.id - update() - - -/// Pictograph display which the AI can use to emote. -/obj/machinery/status_display/ai - name = "\improper AI display" - desc = "A small screen which the AI can use to present itself." - - var/mode = SD_BLANK - var/emotion = "Neutral" - -/obj/machinery/status_display/ai/Initialize() - . = ..() - GLOB.ai_status_displays.Add(src) - -/obj/machinery/status_display/ai/Destroy() - GLOB.ai_status_displays.Remove(src) - . = ..() - -/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user) - if(isAI(user)) - user.ai_statuschange() - -/obj/machinery/status_display/ai/process() - if(mode == SD_BLANK || (stat & NOPOWER)) - remove_display() - return PROCESS_KILL - - if(mode == SD_AI_EMOTE) - switch(emotion) - if("Very Happy") - set_picture("ai_veryhappy") - if("Happy") - set_picture("ai_happy") - if("Neutral") - set_picture("ai_neutral") - if("Unsure") - set_picture("ai_unsure") - if("Confused") - set_picture("ai_confused") - if("Sad") - set_picture("ai_sad") - if("BSOD") - set_picture("ai_bsod") - if("Blank") - set_picture("ai_off") - if("Problems?") - set_picture("ai_trollface") - if("Awesome") - set_picture("ai_awesome") - if("Dorfy") - set_picture("ai_urist") - if("Thinking") - set_picture("ai_thinking") - if("Facepalm") - set_picture("ai_facepalm") - if("Friend Computer") - set_picture("ai_friend") - if("Blue Glow") - set_picture("ai_sal") - if("Red Glow") - set_picture("ai_hal") - return PROCESS_KILL - - if(mode == SD_AI_BSOD) - set_picture("ai_bsod") - return PROCESS_KILL - - -#undef CHARS_PER_LINE -#undef FONT_SIZE -#undef FONT_COLOR -#undef FONT_STYLE -#undef SCROLL_SPEED +// Status display +// (formerly Countdown timer display) + +#define CHARS_PER_LINE 5 +#define FONT_SIZE "5pt" +#define FONT_COLOR "#09f" +#define FONT_STYLE "Arial Black" +#define SCROLL_SPEED 2 + +#define SD_BLANK 0 // 0 = Blank +#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer +#define SD_MESSAGE 2 // 2 = Arbitrary message(s) +#define SD_PICTURE 3 // 3 = alert picture + +#define SD_AI_EMOTE 1 // 1 = AI emoticon +#define SD_AI_BSOD 2 // 2 = Blue screen of death + +/// Status display which can show images and scrolling text. +/obj/machinery/status_display + name = "status display" + desc = null + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + + maptext_height = 26 + maptext_width = 32 + + var/message1 = "" // message line 1 + var/message2 = "" // message line 2 + var/index1 // display index for scrolling messages or 0 if non-scrolling + var/index2 + +/// Immediately blank the display. +/obj/machinery/status_display/proc/remove_display() + cut_overlays() + if(maptext) + maptext = "" + +/// Immediately change the display to the given picture. +/obj/machinery/status_display/proc/set_picture(state) + remove_display() + add_overlay(state) + +/// Immediately change the display to the given two lines. +/obj/machinery/status_display/proc/update_display(line1, line2) + var/new_text = {"
    [line1]
    [line2]
    "} + if(maptext != new_text) + maptext = new_text + +/// Prepare the display to marquee the given two lines. +/// +/// Call with no arguments to disable. +/obj/machinery/status_display/proc/set_message(m1, m2) + if(m1) + index1 = (length(m1) > CHARS_PER_LINE) + message1 = m1 + else + message1 = "" + index1 = 0 + + if(m2) + index2 = (length(m2) > CHARS_PER_LINE) + message2 = m2 + else + message2 = "" + index2 = 0 + +// Timed process - performs default marquee action if so needed. +/obj/machinery/status_display/process() + if(stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + var/line1 = message1 + if(index1) + line1 = copytext("[message1]|[message1]", index1, index1+CHARS_PER_LINE) + var/message1_len = length(message1) + index1 += SCROLL_SPEED + if(index1 > message1_len) + index1 -= message1_len + + var/line2 = message2 + if(index2) + line2 = copytext("[message2]|[message2]", index2, index2+CHARS_PER_LINE) + var/message2_len = length(message2) + index2 += SCROLL_SPEED + if(index2 > message2_len) + index2 -= message2_len + + update_display(line1, line2) + if (!index1 && !index2) + // No marquee, no processing. + return PROCESS_KILL + +/// Update the display and, if necessary, re-enable processing. +/obj/machinery/status_display/proc/update() + if (process() != PROCESS_KILL) + START_PROCESSING(SSmachines, src) + +/obj/machinery/status_display/power_change() + . = ..() + update() + +/obj/machinery/status_display/emp_act(severity) + . = ..() + if(stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF) + return + set_picture("ai_bsod") + +/obj/machinery/status_display/examine(mob/user) + . = ..() + if (message1 || message2) + . += "The display says:" + if (message1) + . += "\t[html_encode(message1)]" + if (message2) + . += "\t[html_encode(message2)]" + +// Helper procs for child display types. +/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle) + if(!shuttle) + // the shuttle is missing - no processing + update_display("shutl?","") + return PROCESS_KILL + else if(shuttle.timer) + var/line1 = "-[shuttle.getModeStr()]-" + var/line2 = shuttle.getTimerStr() + + if(length(line2) > CHARS_PER_LINE) + line2 = "error" + update_display(line1, line2) + else + // don't kill processing, the timer might turn back on + remove_display() + +/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle) + if (shuttle) + var/modestr = shuttle.getModeStr() + if (modestr) + if (shuttle.timer) + modestr = "
    \t[modestr]: [shuttle.getTimerStr()]" + else + modestr = "
    \t[modestr]" + return "The display says:
    \t[shuttle.name][modestr]" + else + return "The display says:
    \tShuttle missing!" + + +/// Evac display which shows shuttle timer or message set by Command. +/obj/machinery/status_display/evac + var/frequency = FREQ_STATUS_DISPLAYS + var/mode = SD_EMERGENCY + var/friendc = FALSE // track if Friend Computer mode + var/last_picture // For when Friend Computer mode is undone + +/obj/machinery/status_display/evac/Initialize() + . = ..() + // register for radio system + SSradio.add_object(src, frequency) + +/obj/machinery/status_display/evac/Destroy() + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/status_display/evac/process() + if(stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + if(friendc) //Makes all status displays except supply shuttle timer display the eye -- Urist + set_picture("ai_friend") + return PROCESS_KILL + + switch(mode) + if(SD_BLANK) + remove_display() + return PROCESS_KILL + + if(SD_EMERGENCY) + return display_shuttle_status(SSshuttle.emergency) + + if(SD_MESSAGE) + return ..() + + if(SD_PICTURE) + set_picture(last_picture) + return PROCESS_KILL + +/obj/machinery/status_display/evac/examine(mob/user) + . = ..() + if(mode == SD_EMERGENCY) + . += examine_shuttle(user, SSshuttle.emergency) + else if(!message1 && !message2) + . += "The display is blank." + +/obj/machinery/status_display/evac/receive_signal(datum/signal/signal) + switch(signal.data["command"]) + if("blank") + mode = SD_BLANK + set_message(null, null) + if("shuttle") + mode = SD_EMERGENCY + set_message(null, null) + if("message") + mode = SD_MESSAGE + set_message(signal.data["msg1"], signal.data["msg2"]) + if("alert") + mode = SD_PICTURE + last_picture = signal.data["picture_state"] + set_picture(last_picture) + if("friendcomputer") + friendc = !friendc + update() + + +/// Supply display which shows the status of the supply shuttle. +/obj/machinery/status_display/supply + name = "supply display" + +/obj/machinery/status_display/supply/process() + if(stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + var/line1 + var/line2 + if(!SSshuttle.supply) + // Might be missing in our first update on initialize before shuttles + // have loaded. Cross our fingers that it will soon return. + line1 = "CARGO" + line2 = "shutl?" + else if(SSshuttle.supply.mode == SHUTTLE_IDLE) + if(is_station_level(SSshuttle.supply.z)) + line1 = "CARGO" + line2 = "Docked" + else + line1 = "CARGO" + line2 = SSshuttle.supply.getTimerStr() + if(length(line2) > CHARS_PER_LINE) + line2 = "Error" + update_display(line1, line2) + +/obj/machinery/status_display/supply/examine(mob/user) + . = ..() + var/obj/docking_port/mobile/shuttle = SSshuttle.supply + var/shuttleMsg = null + if (shuttle.mode == SHUTTLE_IDLE) + if (is_station_level(shuttle.z)) + shuttleMsg = "Docked" + else + shuttleMsg = "[shuttle.getModeStr()]: [shuttle.getTimerStr()]" + if (shuttleMsg) + . += "The display says:
    \t[shuttleMsg]" + else + . += "The display is blank." + + +/// General-purpose shuttle status display. +/obj/machinery/status_display/shuttle + name = "shuttle display" + var/shuttle_id + +/obj/machinery/status_display/shuttle/process() + if(!shuttle_id || (stat & NOPOWER)) + // No power, no processing. + remove_display() + return PROCESS_KILL + + return display_shuttle_status(SSshuttle.getShuttle(shuttle_id)) + +/obj/machinery/status_display/shuttle/examine(mob/user) + . = ..() + if(shuttle_id) + . += examine_shuttle(user, SSshuttle.getShuttle(shuttle_id)) + else + . += "The display is blank." + +/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value) + . = ..() + if(!.) + return + switch(var_name) + if("shuttle_id") + update() + +/obj/machinery/status_display/shuttle/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) + if (port && (shuttle_id == initial(shuttle_id) || override)) + shuttle_id = port.id + update() + + +/// Pictograph display which the AI can use to emote. +/obj/machinery/status_display/ai + name = "\improper AI display" + desc = "A small screen which the AI can use to present itself." + + var/mode = SD_BLANK + var/emotion = "Neutral" + +/obj/machinery/status_display/ai/Initialize() + . = ..() + GLOB.ai_status_displays.Add(src) + +/obj/machinery/status_display/ai/Destroy() + GLOB.ai_status_displays.Remove(src) + . = ..() + +/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user) + if(isAI(user)) + user.ai_statuschange() + +/obj/machinery/status_display/ai/process() + if(mode == SD_BLANK || (stat & NOPOWER)) + remove_display() + return PROCESS_KILL + + if(mode == SD_AI_EMOTE) + switch(emotion) + if("Very Happy") + set_picture("ai_veryhappy") + if("Happy") + set_picture("ai_happy") + if("Neutral") + set_picture("ai_neutral") + if("Unsure") + set_picture("ai_unsure") + if("Confused") + set_picture("ai_confused") + if("Sad") + set_picture("ai_sad") + if("BSOD") + set_picture("ai_bsod") + if("Blank") + set_picture("ai_off") + if("Problems?") + set_picture("ai_trollface") + if("Awesome") + set_picture("ai_awesome") + if("Dorfy") + set_picture("ai_urist") + if("Thinking") + set_picture("ai_thinking") + if("Facepalm") + set_picture("ai_facepalm") + if("Friend Computer") + set_picture("ai_friend") + if("Blue Glow") + set_picture("ai_sal") + if("Red Glow") + set_picture("ai_hal") + return PROCESS_KILL + + if(mode == SD_AI_BSOD) + set_picture("ai_bsod") + return PROCESS_KILL + + +#undef CHARS_PER_LINE +#undef FONT_SIZE +#undef FONT_COLOR +#undef FONT_STYLE +#undef SCROLL_SPEED diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm index 4c85e746..36dbcc1e 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -1,381 +1,381 @@ - -#define MUSICIAN_HEARCHECK_MINDELAY 4 -#define MUSIC_MAXLINES 600 -#define MUSIC_MAXLINECHARS 50 - -/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/edit = 1 // if we're in editing mode - var/repeat = 0 // number of times remaining to repeat - var/max_repeats = 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 - var/last_hearcheck = 0 - var/list/hearing_mobs - -/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) - if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) - LAZYCLEARLIST(hearing_mobs) - for(var/mob/M in get_hearers_in_view(15, source)) - if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) - continue - LAZYADD(hearing_mobs, M) - last_hearcheck = world.time - - var/sound/music_played = sound(soundfile) - for(var/i in hearing_mobs) - var/mob/M = i - M.playsound_local(source, null, 100, falloff = 5, S = music_played) - -/datum/song/proc/updateDialog(mob/user) - instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise - -/datum/song/proc/shouldStopPlaying(mob/user) - if(instrumentObj) - if(!user.canUseTopic(instrumentObj)) - return TRUE - return !instrumentObj.anchored // add special cases to stop in subclasses - else - return TRUE - -/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 = FALSE - hearing_mobs = null - return - if(!length(note)) - 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) - if(user.dizziness > 0 && prob(user.dizziness / 2)) - cur_note = CLAMP(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7) - if(user.dizziness > 0 && prob(user.dizziness / 5)) - if(prob(30)) - cur_acc[cur_note] = "#" - else if(prob(42)) - cur_acc[cur_note] = "b" - else if(prob(75)) - cur_acc[cur_note] = "n" - 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-- - hearing_mobs = null - playing = FALSE - repeat = 0 - updateDialog(user) - -/datum/song/proc/interact(mob/user) - var/dat = "" - - if(lines.len > 0) - dat += "

    Playback

    " - if(!playing) - dat += "Play Stop

    " - dat += "Repeat Song: " - dat += repeat > 0 ? "--" : "--" - dat += " [repeat] times " - dat += repeat < max_repeats ? "++" : "++" - dat += "
    " - else - dat += "Play Stop
    " - dat += "Repeats left: [repeat]
    " - if(!edit) - dat += "
    Show Editor
    " - else - dat += "

    Editing

    " - dat += "Hide Editor" - dat += " Start a New Song" - dat += " Import a Song

    " - var/bpm = round(600 / tempo) - dat += "Tempo: - [bpm] BPM +

    " - var/linecount = 0 - for(var/line in lines) - linecount += 1 - dat += "Line [linecount]: Edit X [line]
    " - dat += "Add Line

    " - if(help) - dat += "Hide Help
    " - dat += {" - Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
    - Every note in a chord will play together, with chord timed by the tempo.
    -
    - Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
    - By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
    - Example: C,D,E,F,G,A,B will play a C major scale.
    - After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
    - Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
    - A pause may be denoted by an empty chord: C,E,,C,G
    - To make a chord be a different time, end it with /x, where the chord length will be length
    - defined by tempo / x: C,G/2,E/4
    - Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 -
    - Lines may be up to [MUSIC_MAXLINECHARS] characters.
    - A song may only contain up to [MUSIC_MAXLINES] lines.
    - "} - else - dat += "Show Help
    " - - var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state)) - popup.open() - -/datum/song/proc/ParseSong(text) - set waitfor = FALSE - //split into lines - lines = splittext(text, "\n") - if(lines.len) - 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 > MUSIC_MAXLINES) - to_chat(usr, "Too many lines!") - lines.Cut(MUSIC_MAXLINES + 1) - var/linenum = 1 - for(var/l in lines) - if(length(l) > MUSIC_MAXLINECHARS) - to_chat(usr, "Line [linenum] too long!") - lines.Remove(l) - else - linenum++ - updateDialog(usr) // make sure updates when complete - -/datum/song/Topic(href, href_list) - if(!usr.canUseTopic(instrumentObj)) - usr << browse(null, "window=instrument") - usr.unset_machine() - return - - instrumentObj.add_fingerprint(usr) - - if(href_list["newsong"]) - lines = new() - tempo = sanitize_tempo(5) // default 120 BPM - name = "" - - else if(href_list["import"]) - 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) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - 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) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - ParseSong(t) - - else if(href_list["help"]) - help = text2num(href_list["help"]) - 1 - - else if(href_list["edit"]) - edit = text2num(href_list["edit"]) - 1 - - 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_repeats) - repeat = max_repeats - - else if(href_list["tempo"]) - tempo = sanitize_tempo(tempo + text2num(href_list["tempo"])) - - else if(href_list["play"]) - playing = TRUE - spawn() - playsong(usr) - - else if(href_list["newline"]) - var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) - if(!newline || !in_range(instrumentObj, usr)) - return - if(lines.len > MUSIC_MAXLINES) - return - if(length(newline) > MUSIC_MAXLINECHARS) - newline = copytext(newline, 1, MUSIC_MAXLINECHARS) - lines.Add(newline) - - else if(href_list["deleteline"]) - var/num = round(text2num(href_list["deleteline"])) - if(num > lines.len || num < 1) - return - lines.Cut(num, num+1) - - else if(href_list["modifyline"]) - var/num = round(text2num(href_list["modifyline"]),1) - 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) > MUSIC_MAXLINECHARS) - content = copytext(content, 1, MUSIC_MAXLINECHARS) - if(num > lines.len || num < 1) - return - lines[num] = content - - else if(href_list["stop"]) - playing = FALSE - hearing_mobs = null - - updateDialog(usr) - return - -/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/updateDialog(mob/user) - instrumentObj.interact(user) - -/datum/song/handheld/shouldStopPlaying() - if(instrumentObj) - return !isliving(instrumentObj.loc) - else - return TRUE - - -////////////////////////////////////////////////////////////////////////// - - -/obj/structure/piano - name = "space minimoog" - icon = 'icons/obj/musician.dmi' - icon_state = "minimoog" - anchored = TRUE - density = TRUE - var/datum/song/song - -/obj/structure/piano/unanchored - anchored = FALSE - -/obj/structure/piano/New() - ..() - song = new("piano", src) - - if(prob(50) && icon_state == initial(icon_state)) - 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(song) - song = null - return ..() - -/obj/structure/piano/Initialize(mapload) - . = ..() - if(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - -/obj/structure/piano/attack_hand(mob/user) - . = ..() - if(.) - return - interact(user) - -/obj/structure/piano/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/piano/interact(mob/user) - ui_interact(user) - -/obj/structure/piano/ui_interact(mob/user) - if(!user || !anchored) - return - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - user.set_machine(src) - song.interact(user) - -/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) - default_unfasten_wrench(user, I, 40) - return TRUE + +#define MUSICIAN_HEARCHECK_MINDELAY 4 +#define MUSIC_MAXLINES 600 +#define MUSIC_MAXLINECHARS 50 + +/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/edit = 1 // if we're in editing mode + var/repeat = 0 // number of times remaining to repeat + var/max_repeats = 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 + var/last_hearcheck = 0 + var/list/hearing_mobs + +/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) + if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) + LAZYCLEARLIST(hearing_mobs) + for(var/mob/M in get_hearers_in_view(15, source)) + if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) + continue + LAZYADD(hearing_mobs, M) + last_hearcheck = world.time + + var/sound/music_played = sound(soundfile) + for(var/i in hearing_mobs) + var/mob/M = i + M.playsound_local(source, null, 100, falloff = 5, S = music_played) + +/datum/song/proc/updateDialog(mob/user) + instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise + +/datum/song/proc/shouldStopPlaying(mob/user) + if(instrumentObj) + if(!user.canUseTopic(instrumentObj)) + return TRUE + return !instrumentObj.anchored // add special cases to stop in subclasses + else + return TRUE + +/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 = FALSE + hearing_mobs = null + return + if(!length(note)) + 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) + if(user.dizziness > 0 && prob(user.dizziness / 2)) + cur_note = CLAMP(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7) + if(user.dizziness > 0 && prob(user.dizziness / 5)) + if(prob(30)) + cur_acc[cur_note] = "#" + else if(prob(42)) + cur_acc[cur_note] = "b" + else if(prob(75)) + cur_acc[cur_note] = "n" + 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-- + hearing_mobs = null + playing = FALSE + repeat = 0 + updateDialog(user) + +/datum/song/proc/interact(mob/user) + var/dat = "" + + if(lines.len > 0) + dat += "

    Playback

    " + if(!playing) + dat += "Play Stop

    " + dat += "Repeat Song: " + dat += repeat > 0 ? "--" : "--" + dat += " [repeat] times " + dat += repeat < max_repeats ? "++" : "++" + dat += "
    " + else + dat += "Play Stop
    " + dat += "Repeats left: [repeat]
    " + if(!edit) + dat += "
    Show Editor
    " + else + dat += "

    Editing

    " + dat += "Hide Editor" + dat += " Start a New Song" + dat += " Import a Song

    " + var/bpm = round(600 / tempo) + dat += "Tempo: - [bpm] BPM +

    " + var/linecount = 0 + for(var/line in lines) + linecount += 1 + dat += "Line [linecount]: Edit X [line]
    " + dat += "Add Line

    " + if(help) + dat += "Hide Help
    " + dat += {" + Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
    + Every note in a chord will play together, with chord timed by the tempo.
    +
    + Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
    + By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
    + Example: C,D,E,F,G,A,B will play a C major scale.
    + After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
    + Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
    + A pause may be denoted by an empty chord: C,E,,C,G
    + To make a chord be a different time, end it with /x, where the chord length will be length
    + defined by tempo / x: C,G/2,E/4
    + Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 +
    + Lines may be up to [MUSIC_MAXLINECHARS] characters.
    + A song may only contain up to [MUSIC_MAXLINES] lines.
    + "} + else + dat += "Show Help
    " + + var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state)) + popup.open() + +/datum/song/proc/ParseSong(text) + set waitfor = FALSE + //split into lines + lines = splittext(text, "\n") + if(lines.len) + 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 > MUSIC_MAXLINES) + to_chat(usr, "Too many lines!") + lines.Cut(MUSIC_MAXLINES + 1) + var/linenum = 1 + for(var/l in lines) + if(length(l) > MUSIC_MAXLINECHARS) + to_chat(usr, "Line [linenum] too long!") + lines.Remove(l) + else + linenum++ + updateDialog(usr) // make sure updates when complete + +/datum/song/Topic(href, href_list) + if(!usr.canUseTopic(instrumentObj)) + usr << browse(null, "window=instrument") + usr.unset_machine() + return + + instrumentObj.add_fingerprint(usr) + + if(href_list["newsong"]) + lines = new() + tempo = sanitize_tempo(5) // default 120 BPM + name = "" + + else if(href_list["import"]) + 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) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + 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) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + ParseSong(t) + + else if(href_list["help"]) + help = text2num(href_list["help"]) - 1 + + else if(href_list["edit"]) + edit = text2num(href_list["edit"]) - 1 + + 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_repeats) + repeat = max_repeats + + else if(href_list["tempo"]) + tempo = sanitize_tempo(tempo + text2num(href_list["tempo"])) + + else if(href_list["play"]) + playing = TRUE + spawn() + playsong(usr) + + else if(href_list["newline"]) + var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) + if(!newline || !in_range(instrumentObj, usr)) + return + if(lines.len > MUSIC_MAXLINES) + return + if(length(newline) > MUSIC_MAXLINECHARS) + newline = copytext(newline, 1, MUSIC_MAXLINECHARS) + lines.Add(newline) + + else if(href_list["deleteline"]) + var/num = round(text2num(href_list["deleteline"])) + if(num > lines.len || num < 1) + return + lines.Cut(num, num+1) + + else if(href_list["modifyline"]) + var/num = round(text2num(href_list["modifyline"]),1) + 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) > MUSIC_MAXLINECHARS) + content = copytext(content, 1, MUSIC_MAXLINECHARS) + if(num > lines.len || num < 1) + return + lines[num] = content + + else if(href_list["stop"]) + playing = FALSE + hearing_mobs = null + + updateDialog(usr) + return + +/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/updateDialog(mob/user) + instrumentObj.interact(user) + +/datum/song/handheld/shouldStopPlaying() + if(instrumentObj) + return !isliving(instrumentObj.loc) + else + return TRUE + + +////////////////////////////////////////////////////////////////////////// + + +/obj/structure/piano + name = "space minimoog" + icon = 'icons/obj/musician.dmi' + icon_state = "minimoog" + anchored = TRUE + density = TRUE + var/datum/song/song + +/obj/structure/piano/unanchored + anchored = FALSE + +/obj/structure/piano/New() + ..() + song = new("piano", src) + + if(prob(50) && icon_state == initial(icon_state)) + 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(song) + song = null + return ..() + +/obj/structure/piano/Initialize(mapload) + . = ..() + if(mapload) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + +/obj/structure/piano/attack_hand(mob/user) + . = ..() + if(.) + return + interact(user) + +/obj/structure/piano/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/piano/interact(mob/user) + ui_interact(user) + +/obj/structure/piano/ui_interact(mob/user) + if(!user || !anchored) + return + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + user.set_machine(src) + song.interact(user) + +/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I, 40) + return TRUE diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 8b45bb47..09cfb2d1 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1,2991 +1,2991 @@ -/datum/admins/proc/CheckAdminHref(href, href_list) - var/auth = href_list["admin_token"] - . = auth && (auth == href_token || auth == GLOB.href_token) - if(.) - return - var/msg = !auth ? "no" : "a bad" - message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") - if(CONFIG_GET(flag/debug_admin_hrefs)) - message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") - log_world("UAH: [href]") - return TRUE - log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") - -/datum/admins/Topic(href, href_list) - ..() - - if(usr.client != src.owner || !check_rights(0)) - message_admins("[usr.key] tried to use the admin panel without authorization.") - log_admin("[key_name(usr)] tried to use the admin panel without authorization.") - return - - if(!CheckAdminHref(href, href_list)) - return - - if(href_list["makementor"]) - makeMentor(href_list["makementor"]) - else if(href_list["removementor"]) - removeMentor(href_list["removementor"]) - - if(href_list["ahelp"]) - if(!check_rights(R_ADMIN, TRUE)) - return - - var/ahelp_ref = href_list["ahelp"] - var/datum/admin_help/AH = locate(ahelp_ref) - if(AH) - AH.Action(href_list["ahelp_action"]) - else - to_chat(usr, "Ticket [ahelp_ref] has been deleted!") - - else if(href_list["ahelp_tickets"]) - GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"])) - - else if(href_list["stickyban"]) - stickyban(href_list["stickyban"],href_list) - - else if(href_list["getplaytimewindow"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["getplaytimewindow"]) in GLOB.mob_list - if(!M) - to_chat(usr, "ERROR: Mob not found.") - return - cmd_show_exp_panel(M.client) - - else if(href_list["toggleexempt"]) - if(!check_rights(R_ADMIN)) - return - var/client/C = locate(href_list["toggleexempt"]) in GLOB.clients - if(!C) - to_chat(usr, "ERROR: Client not found.") - return - toggle_exempt_status(C) - - else if(href_list["makeAntag"]) - if(!check_rights(R_ADMIN)) - return - if (!SSticker.mode) - to_chat(usr, "Not until the round starts!") - return - switch(href_list["makeAntag"]) - if("traitors") - if(src.makeTraitors()) - message_admins("[key_name_admin(usr)] created traitors.") - log_admin("[key_name(usr)] created traitors.") - else - message_admins("[key_name_admin(usr)] tried to create traitors. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create traitors.") - if("changelings") - if(src.makeChangelings()) - message_admins("[key_name(usr)] created changelings.") - log_admin("[key_name(usr)] created changelings.") - else - message_admins("[key_name_admin(usr)] tried to create changelings. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create changelings.") - if("revs") - if(src.makeRevs()) - message_admins("[key_name(usr)] started a revolution.") - log_admin("[key_name(usr)] started a revolution.") - else - message_admins("[key_name_admin(usr)] tried to start a revolution. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a revolution.") - if("cult") - if(src.makeCult()) - message_admins("[key_name(usr)] started a cult.") - log_admin("[key_name(usr)] started a cult.") - else - message_admins("[key_name_admin(usr)] tried to start a cult. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a cult.") - if("wizard") - message_admins("[key_name(usr)] is creating a wizard...") - if(src.makeWizard()) - message_admins("[key_name(usr)] created a wizard.") - log_admin("[key_name(usr)] created a wizard.") - else - message_admins("[key_name_admin(usr)] tried to create a wizard. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a wizard.") - if("nukeops") - message_admins("[key_name(usr)] is creating a nuke team...") - if(src.makeNukeTeam()) - message_admins("[key_name(usr)] created a nuke team.") - log_admin("[key_name(usr)] created a nuke team.") - else - message_admins("[key_name_admin(usr)] tried to create a nuke team. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a nuke team.") - if("ninja") - message_admins("[key_name(usr)] spawned a ninja.") - log_admin("[key_name(usr)] spawned a ninja.") - src.makeSpaceNinja() - if("aliens") - message_admins("[key_name(usr)] started an alien infestation.") - log_admin("[key_name(usr)] started an alien infestation.") - src.makeAliens() - if("deathsquad") - message_admins("[key_name(usr)] is creating a death squad...") - if(src.makeDeathsquad()) - message_admins("[key_name(usr)] created a death squad.") - log_admin("[key_name(usr)] created a death squad.") - else - message_admins("[key_name_admin(usr)] tried to create a death squad. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a death squad.") - if("blob") - var/strength = input("Set Blob Resource Gain Rate","Set Resource Rate",1) as num|null - if(!strength) - return - message_admins("[key_name(usr)] spawned a blob with base resource gain [strength].") - log_admin("[key_name(usr)] spawned a blob with base resource gain [strength].") - new/datum/round_event/ghost_role/blob(TRUE, strength) - if("centcom") - message_admins("[key_name(usr)] is creating a CentCom response team...") - if(src.makeEmergencyresponseteam()) - message_admins("[key_name(usr)] created a CentCom response team.") - log_admin("[key_name(usr)] created a CentCom response team.") - else - message_admins("[key_name_admin(usr)] tried to create a CentCom response team. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a CentCom response team.") - if("abductors") - message_admins("[key_name(usr)] is creating an abductor team...") - if(src.makeAbductorTeam()) - message_admins("[key_name(usr)] created an abductor team.") - log_admin("[key_name(usr)] created an abductor team.") - else - message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunatly there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create an abductor team.") - if("clockcult") - if(src.makeClockCult()) - message_admins("[key_name(usr)] started a clockwork cult.") - log_admin("[key_name(usr)] started a clockwork cult.") - else - message_admins("[key_name_admin(usr)] tried to start a clockwork cult. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a clockwork cult.") - if("revenant") - if(src.makeRevenant()) - message_admins("[key_name(usr)] created a revenant.") - log_admin("[key_name(usr)] created a revenant.") - else - message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a revenant.") - //Hyper - if("lewdtraitors") - if(src.makeLewdtraitors()) - message_admins("[key_name(usr)] created a lewd traitor.") - log_admin("[key_name(usr)] created a lewd traitor.") - else - message_admins("[key_name_admin(usr)] tried to create a lewd traitor. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a lewd traitor.") - - else if(href_list["forceevent"]) - if(!check_rights(R_FUN)) - return - var/datum/round_event_control/E = locate(href_list["forceevent"]) in SSevents.control - if(E) - E.admin_setup(usr) - var/datum/round_event/event = E.runEvent() - if(event.announceWhen>0) - event.processing = FALSE - var/prompt = alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel") - switch(prompt) - if("Cancel") - event.kill() - return - if("No") - event.announceWhen = -1 - event.processing = TRUE - message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])") - log_admin("[key_name(usr)] has triggered an event. ([E.name])") - return - - else if(href_list["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"]) - var/adminckey = href_list["dbsearchadmin"] - var/playerckey = href_list["dbsearchckey"] - var/ip = href_list["dbsearchip"] - var/cid = href_list["dbsearchcid"] - var/page = href_list["dbsearchpage"] - - DB_ban_panel(playerckey, adminckey, ip, cid, page) - 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"]) - if(!check_rights(R_BAN)) - return - var/bantype = text2num(href_list["dbbanaddtype"]) - var/bankey = href_list["dbbanaddkey"] - var/banckey = ckey(bankey) - 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"] - var/banseverity = href_list["dbbanaddseverity"] - - switch(bantype) - if(BANTYPE_PERMA) - if(!banckey || !banreason || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, and reason).") - return - banduration = null - banjob = null - if(BANTYPE_TEMP) - if(!banckey || !banreason || !banduration || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, reason, severity and duration).") - return - banjob = null - if(BANTYPE_JOB_PERMA) - if(!banckey || !banreason || !banjob || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") - return - banduration = null - if(BANTYPE_JOB_TEMP) - if(!banckey || !banreason || !banjob || !banduration || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") - return - if(BANTYPE_ADMIN_PERMA) - if(!banckey || !banreason || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity and reason).") - return - banduration = null - banjob = null - if(BANTYPE_ADMIN_TEMP) - if(!banckey || !banreason || !banduration || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, 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.key] 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.") - - if(!DB_ban_record(bantype, playermob, banduration, banreason, banjob, bankey, banip, bancid )) - to_chat(usr, "Failed to apply ban.") - return - create_message("note", bankey, null, banreason, null, null, 0, 0, null, 0, banseverity) - - else if(href_list["editrightsbrowser"]) - edit_admin_permissions(0) - - else if(href_list["editrightsbrowserlog"]) - edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"]) - - if(href_list["editrightsbrowsermanage"]) - if(href_list["editrightschange"]) - change_admin_rank(ckey(href_list["editrightschange"]), href_list["editrightschange"], TRUE) - else if(href_list["editrightsremove"]) - remove_admin(ckey(href_list["editrightsremove"]), href_list["editrightsremove"], TRUE) - else if(href_list["editrightsremoverank"]) - remove_rank(href_list["editrightsremoverank"]) - edit_admin_permissions(2) - - else if(href_list["editrights"]) - edit_rights_topic(href_list) - - else if(href_list["gamemode_panel"]) - if(!check_rights(R_ADMIN)) - return - SSticker.mode.admin_panel() - - - else if(href_list["call_shuttle"]) - if(!check_rights(R_ADMIN)) - return - - - switch(href_list["call_shuttle"]) - if("1") - if(EMERGENCY_AT_LEAST_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(EMERGENCY_AT_LEAST_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|null - if(!timer) - return - SSshuttle.emergency.setTimer(timer*10) - log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") - minor_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["trigger_centcom_recall"]) - if(!check_rights(R_ADMIN)) - return - - usr.client.trigger_centcom_recall() - - else if(href_list["toggle_continuous"]) - if(!check_rights(R_ADMIN)) - return - var/list/continuous = CONFIG_GET(keyed_list/continuous) - if(!continuous[SSticker.mode.config_tag]) - continuous[SSticker.mode.config_tag] = TRUE - else - continuous[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") - check_antagonists() - - else if(href_list["toggle_midround_antag"]) - if(!check_rights(R_ADMIN)) - return - - var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) - if(!midround_antag[SSticker.mode.config_tag]) - midround_antag[SSticker.mode.config_tag] = TRUE - else - midround_antag[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") - check_antagonists() - - else if(href_list["alter_midround_time_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null - if(!timer) - return - CONFIG_SET(number/midround_antag_time_check, timer) - message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") - check_antagonists() - - else if(href_list["alter_midround_life_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num - if(!ratio) - return - CONFIG_SET(number/midround_antag_life_check, ratio / 100) - - message_admins("[key_name_admin(usr)] edited the midround antagonist living crew ratio to [ratio]% alive.") - check_antagonists() - - else if(href_list["toggle_noncontinuous_behavior"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.mode.round_ends_with_antag_death) - SSticker.mode.round_ends_with_antag_death = 1 - else - SSticker.mode.round_ends_with_antag_death = 0 - - message_admins("[key_name_admin(usr)] edited the midround antagonist system to [SSticker.mode.round_ends_with_antag_death ? "end the round" : "continue as extended"] upon failure.") - check_antagonists() - - else if(href_list["delay_round_end"]) - if(!check_rights(R_SERVER)) - return - if(!SSticker.delay_end) - SSticker.admin_delay_notice = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text - if(isnull(SSticker.admin_delay_notice)) - return - else - SSticker.admin_delay_notice = null - SSticker.delay_end = !SSticker.delay_end - var/reason = SSticker.delay_end ? "for reason: [SSticker.admin_delay_notice]" : "."//laziness - var/msg = "[SSticker.delay_end ? "delayed" : "undelayed"] the round end [reason]" - log_admin("[key_name(usr)] [msg]") - message_admins("[key_name_admin(usr)] [msg]") - href_list["secrets"] = "check_antagonist" - if(SSticker.ready_for_reboot && !SSticker.delay_end) //we undelayed after standard reboot would occur - SSticker.standard_reboot() - - else if(href_list["end_round"]) - if(!check_rights(R_ADMIN)) - return - - message_admins("[key_name_admin(usr)] is considering ending the round.") - if(alert(usr, "This will end the round, are you SURE you want to do this?", "Confirmation", "Yes", "No") == "Yes") - if(alert(usr, "Final Confirmation: End the round NOW?", "Confirmation", "Yes", "No") == "Yes") - message_admins("[key_name_admin(usr)] has ended the round.") - SSticker.force_ending = 1 //Yeah there we go APC destroyed mission accomplished - return - else - message_admins("[key_name_admin(usr)] decided against ending the round.") - else - message_admins("[key_name_admin(usr)] decided against ending the round.") - - else if(href_list["simplemake"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/M = locate(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 - - 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]") - switch(href_list["simplemake"]) - if("observer") - M.change_mob_type( /mob/dead/observer , null, null, delmob ) - if("drone") - M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) - if("hunter") - M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) - if("queen") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) - if("praetorian") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) - if("sentinel") - M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) - if("larva") - M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) - 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 ) - if(posttransformoutfit && istype(newmob)) - newmob.equipOutfit(posttransformoutfit) - if("slime") - M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) - if("monkey") - M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) - if("robot") - M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) - if("cat") - M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) - if("runtime") - M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob ) - if("corgi") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) - if("ian") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/Ian , null, null, delmob ) - if("pug") - M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) - if("crab") - M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) - if("coffee") - M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob ) - if("parrot") - M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) - if("polyparrot") - M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob ) - if("constructarmored") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/armored , null, null, delmob ) - if("constructbuilder") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/builder , null, null, delmob ) - if("constructwraith") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob ) - if("shade") - M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob ) - - - /////////////////////////////////////new ban stuff - else if(href_list["unbanf"]) - if(!check_rights(R_BAN)) - return - - var/banfolder = href_list["unbanf"] - GLOB.Banlist.cd = "/base/[banfolder]" - var/key = GLOB.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["unbane"]) - if(!check_rights(R_BAN)) - return - - UpdateTime() - var/reason - - var/banfolder = href_list["unbane"] - GLOB.Banlist.cd = "/base/[banfolder]" - var/reason2 = GLOB.Banlist["reason"] - var/temp = GLOB.Banlist["temp"] - - var/minutes = GLOB.Banlist["minutes"] - - var/banned_key = GLOB.Banlist["key"] - GLOB.Banlist.cd = "/base" - - var/duration - - switch(alert("Temporary Ban for [banned_key]?",,"Yes","No")) - if("Yes") - temp = 1 - var/mins = 0 - if(minutes > GLOB.CMinutes) - mins = minutes - GLOB.CMinutes - mins = input(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440) as num|null - if(mins <= 0) - to_chat(usr, "[mins] is not a valid duration.") - return - minutes = GLOB.CMinutes + mins - duration = GetExp(minutes) - reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null - if(!reason) - return - if("No") - temp = 0 - duration = "Perma" - reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null - if(!reason) - return - - log_admin_private("[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]") - GLOB.Banlist.cd = "/base/[banfolder]" - WRITE_FILE(GLOB.Banlist["reason"], reason) - WRITE_FILE(GLOB.Banlist["temp"], temp) - WRITE_FILE(GLOB.Banlist["minutes"], minutes) - WRITE_FILE(GLOB.Banlist["bannedby"], usr.ckey) - GLOB.Banlist.cd = "/base" - unbanpanel() - - /////////////////////////////////////new ban stuff - - else if(href_list["appearanceban"]) - if(!check_rights(R_BAN)) - return - var/mob/M = locate(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 - - - if(jobban_isbanned(M, "appearance")) - switch(alert("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_private("[key_name(usr)] removed [key_name(M)]'s appearance ban.") - DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, "appearance") - if(M.client) - jobban_buildcache(M.client) - message_admins("[key_name_admin(usr)] removed [key_name_admin(M)]'s appearance ban.") - to_chat(M, "[usr.client.key] has removed your appearance ban.") - - else switch(alert("Appearance ban [M.key]?",,"Yes","No", "Cancel")) - if("Yes") - var/reason = input(usr,"Please State Reason.","Reason") as message|null - if(!reason) - return - var/severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") - if(!severity) - return - if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, "appearance")) - to_chat(usr, "Failed to apply ban.") - return - if(M.client) - jobban_buildcache(M.client) - ban_unban_log_save("[key_name(usr)] appearance banned [key_name(M)]. reason: [reason]") - log_admin_private("[key_name(usr)] appearance banned [key_name(M)]. \nReason: [reason]") - create_message("note", M.key, null, "Appearance banned - [reason]", null, null, 0, 0, null, 0, severity) - message_admins("[key_name_admin(usr)] appearance banned [key_name_admin(M)].") - to_chat(M, "You have been appearance banned by [usr.client.key].") - to_chat(M, "The reason is: [reason]") - to_chat(M, "Appearance ban can be lifted only upon request.") - var/bran = CONFIG_GET(string/banappeals) - if(bran) - to_chat(M, "To try to resolve this matter head to [bran]") - 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 = locate(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 - - var/dat = "Job-Ban Panel: [key_name(M)]" - - /***********************************WARNING!************************************ - The jobban stuff looks mangled and disgusting - But it looks beautiful in-game - -Nodrak - ************************************WARNING!***********************************/ - var/counter = 0 -//Regular jobs - //Command (Blue) - dat += "" - dat += "" - for(var/jobPos in GLOB.command_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 6) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
    Command Positions
    [jobPos][jobPos]
    " - - //Security (Red) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.security_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
    Security Positions
    [jobPos][jobPos]
    " - - //Engineering (Yellow) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.engineering_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
    Engineering Positions
    [jobPos][jobPos]
    " - - //Medical (White) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.medical_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
    Medical Positions
    [jobPos][jobPos]
    " - - //Science (Purple) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.science_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
    Science Positions
    [jobPos][jobPos]
    " - - //Supply (Brown) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.supply_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get COPYPASTE! - dat += "" - counter = 0 - dat += "
    Supply Positions
    [jobPos][jobPos]
    " - - //Civilian (Grey) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.civilian_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
    Civilian Positions
    [jobPos][jobPos]
    " - - //Non-Human (Green) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.nonhuman_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - - dat += "
    Non-human Positions
    [jobPos][jobPos]
    " - - //Ghost Roles (light light gray) - dat += "" - dat += "" - - //pAI - if(jobban_isbanned(M, ROLE_PAI)) - dat += "" - else - dat += "" - - - //Drones - if(jobban_isbanned(M, ROLE_DRONE)) - dat += "" - else - dat += "" - - - //Positronic Brains - if(jobban_isbanned(M, ROLE_POSIBRAIN)) - dat += "" - else - dat += "" - - //Sentience Potion Spawn - if(jobban_isbanned(M, ROLE_SENTIENCE)) - dat += "" - else - dat += "" - - //Deathsquad - if(jobban_isbanned(M, ROLE_DEATHSQUAD)) - dat += "" - else - dat += "" - - //Lavaland roles - if(jobban_isbanned(M, ROLE_LAVALAND)) - dat += "" - else - dat += "" - - dat += "
    Ghost Roles
    pAIpAIDroneDronePosibrainPosibrainSentience Potion SpawnSentience Potion SpawnDeathsquadDeathsquadLavalandLavaland
    " - - //Antagonist (Orange) - var/isbanned_dept = jobban_isbanned(M, ROLE_SYNDICATE) - dat += "" - dat += "" - - //Traitor - if(jobban_isbanned(M, ROLE_TRAITOR) || isbanned_dept) - dat += "" - else - dat += "" - - //Changeling - if(jobban_isbanned(M, ROLE_CHANGELING) || isbanned_dept) - dat += "" - else - dat += "" - - //Nuke Operative - if(jobban_isbanned(M, ROLE_OPERATIVE) || isbanned_dept) - dat += "" - else - dat += "" - - //Revolutionary - if(jobban_isbanned(M, ROLE_REV) || isbanned_dept) - dat += "" - else - dat += "" - - //Cultist - if(jobban_isbanned(M, ROLE_CULTIST) || isbanned_dept) - dat += "" - else - dat += "" - - dat += "" //So things dont get squished. - - //Servant of Ratvar - if(jobban_isbanned(M, ROLE_SERVANT_OF_RATVAR) || isbanned_dept) - dat += "" - else - dat += "" - - //Wizard - if(jobban_isbanned(M, ROLE_WIZARD) || isbanned_dept) - dat += "" - else - dat += "" - - //Abductor - if(jobban_isbanned(M, ROLE_ABDUCTOR) || isbanned_dept) - dat += "" - else - dat += "" - - //Alien - if(jobban_isbanned(M, ROLE_ALIEN) || isbanned_dept) - dat += "" - else - dat += "" - - //Gang - if(jobban_isbanned(M, ROLE_GANG) || isbanned_dept) - dat += "" - else - dat += "" - - - //Other Roles (black) - dat += "
    Antagonist Positions | " - dat += "Team Antagonists | " - dat += "Conversion Antagonists
    TraitorTraitorChangelingChangelingNuke OperativeNuke OperativeRevolutionaryRevolutionaryCultistCultist
    ServantServantWizardWizardAbductorAbductorAlienAlienGangGang
    " - dat += "" - - //Mind Transfer Potion - if(jobban_isbanned(M, ROLE_MIND_TRANSFER)) - dat += "" - else - dat += "" - - dat += "
    Other Roles
    Mind Transfer PotionMind Transfer Potion
    " - usr << browse(dat, "window=jobban2;size=800x450") - return - - //JOBBAN'S INNARDS - else if(href_list["jobban3"]) - if(!check_rights(R_BAN)) - return - var/mob/M = locate(href_list["jobban4"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(!SSjob) - to_chat(usr, "Jobs subsystem not initialized yet!") - return - //get jobs for department if specified, otherwise just return the one job in a list. - var/list/joblist = list() - switch(href_list["jobban3"]) - if("commanddept") - for(var/jobPos in GLOB.command_positions) - if(!jobPos) - continue - joblist += jobPos - if("securitydept") - for(var/jobPos in GLOB.security_positions) - if(!jobPos) - continue - joblist += jobPos - if("engineeringdept") - for(var/jobPos in GLOB.engineering_positions) - if(!jobPos) - continue - joblist += jobPos - if("medicaldept") - for(var/jobPos in GLOB.medical_positions) - if(!jobPos) - continue - joblist += jobPos - if("sciencedept") - for(var/jobPos in GLOB.science_positions) - if(!jobPos) - continue - joblist += jobPos - if("supplydept") - for(var/jobPos in GLOB.supply_positions) - if(!jobPos) - continue - joblist += jobPos - if("civiliandept") - for(var/jobPos in GLOB.civilian_positions) - if(!jobPos) - continue - joblist += jobPos - if("nonhumandept") - for(var/jobPos in GLOB.nonhuman_positions) - if(!jobPos) - continue - joblist += jobPos - if("ghostroles") - joblist += list(ROLE_PAI, ROLE_POSIBRAIN, ROLE_DRONE , ROLE_DEATHSQUAD, ROLE_LAVALAND, ROLE_SENTIENCE) - if("teamantags") - joblist += list(ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_GANG) - if("convertantags") - joblist += list(ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ALIEN) - if("otherroles") - joblist += list(ROLE_MIND_TRANSFER) - 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. - var/severity = null - switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) - if("Yes") - var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null - if(mins <= 0) - to_chat(usr, "[mins] is not a valid duration.") - return - var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null - if(!reason) - return - severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") - if(!severity) - return - var/msg - for(var/job in notbannedlist) - if(!DB_ban_record(BANTYPE_JOB_TEMP, M, mins, reason, job)) - to_chat(usr, "Failed to apply ban.") - return - if(M.client) - jobban_buildcache(M.client) - ban_unban_log_save("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes. reason: [reason]") - log_admin_private("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes.") - if(!msg) - msg = job - else - msg += ", [job]" - create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) - message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg] for [mins] minutes.") - to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] 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 Reason For Banning [M.key].","Reason") as message|null - severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") - if(!severity) - return - if(reason) - var/msg - for(var/job in notbannedlist) - if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, job)) - to_chat(usr, "Failed to apply ban.") - return - if(M.client) - jobban_buildcache(M.client) - ban_unban_log_save("[key_name(usr)] perma-jobbanned [key_name(M)] from [job]. reason: [reason]") - log_admin_private("[key_name(usr)] perma-banned [key_name(M)] from [job]") - if(!msg) - msg = job - else - msg += ", [job]" - create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) - message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg].") - to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] 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. - 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_private("[key_name(usr)] unbanned [key_name(M)] from [job]") - DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, job) - if(M.client) - jobban_buildcache(M.client) - if(!msg) - msg = job - else - msg += ", [job]" - else - continue - if(msg) - message_admins("[key_name_admin(usr)] unbanned [key_name_admin(M)] from [msg].") - to_chat(M, "You have been un-jobbanned by [usr.client.key] 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"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["boot2"]) - if(ismob(M)) - if(!check_if_greater_rights_than(M.client)) - to_chat(usr, "Error: They have more rights than you do.") - return - if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") - return - if(!M) - to_chat(usr, "Error: [M] no longer exists!") - return - if(!M.client) - to_chat(usr, "Error: [M] no longer has a client!") - return - to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].") - log_admin("[key_name(usr)] kicked [key_name(M)].") - message_admins("[key_name_admin(usr)] kicked [key_name_admin(M)].") - qdel(M.client) - - else if(href_list["addmessage"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addmessage"] - create_message("message", target_key, secret = 0) - - else if(href_list["addnote"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addnote"] - create_message("note", target_key) - - else if(href_list["addwatch"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addwatch"] - create_message("watchlist entry", target_key, secret = 1) - - else if(href_list["addmemo"]) - if(!check_rights(R_ADMIN)) - return - create_message("memo", secret = 0, browse = 1) - - else if(href_list["addmessageempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("message", secret = 0) - - else if(href_list["addnoteempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("note") - - else if(href_list["addwatchempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("watchlist entry", secret = 1) - - else if(href_list["deletemessage"]) - if(!check_rights(R_ADMIN)) - return - var/safety = alert("Delete message/note?",,"Yes","No"); - if (safety == "Yes") - var/message_id = href_list["deletemessage"] - delete_message(message_id) - - else if(href_list["deletemessageempty"]) - if(!check_rights(R_ADMIN)) - return - var/safety = alert("Delete message/note?",,"Yes","No"); - if (safety == "Yes") - var/message_id = href_list["deletemessageempty"] - delete_message(message_id, browse = TRUE) - - else if(href_list["editmessage"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessage"] - edit_message(message_id) - - else if(href_list["editmessageempty"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageempty"] - edit_message(message_id, browse = 1) - - else if(href_list["editmessageexpiry"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageexpiry"] - edit_message_expiry(message_id) - - else if(href_list["editmessageexpiryempty"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageexpiryempty"] - edit_message_expiry(message_id, browse = 1) - - else if(href_list["editmessageseverity"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageseverity"] - edit_message_severity(message_id) - - else if(href_list["secretmessage"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["secretmessage"] - toggle_message_secrecy(message_id) - - else if(href_list["searchmessages"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["searchmessages"] - browse_messages(index = target) - - else if(href_list["nonalpha"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["nonalpha"] - target = text2num(target) - browse_messages(index = target) - - else if(href_list["showmessages"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["showmessages"] - browse_messages(index = target) - - else if(href_list["showmemo"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("memo") - - else if(href_list["showwatch"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("watchlist entry") - - else if(href_list["showwatchfilter"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("watchlist entry", filter = 1) - - else if(href_list["showmessageckey"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["showmessageckey"] - var/agegate = TRUE - if (href_list["showall"]) - agegate = FALSE - browse_messages(target_ckey = target, agegate = agegate) - - else if(href_list["showmessageckeylinkless"]) - var/target = href_list["showmessageckeylinkless"] - browse_messages(target_ckey = target, linkless = 1) - - else if(href_list["messageedits"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = sanitizeSQL("[href_list["messageedits"]]") - var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") - if(!query_get_message_edits.warn_execute()) - qdel(query_get_message_edits) - return - if(query_get_message_edits.NextRow()) - var/edit_log = query_get_message_edits.item[1] - if(!QDELETED(usr)) - var/datum/browser/browser = new(usr, "Note edits", "Note edits") - browser.set_content(jointext(edit_log, "")) - browser.open() - qdel(query_get_message_edits) - - else if(href_list["newban"]) - if(!check_rights(R_BAN)) - return - - var/mob/M = locate(href_list["newban"]) - if(!ismob(M)) - return - - if(M.client && M.client.holder) - return //admins cannot be banned. Even if they could, the ban doesn't affect them anyway - - switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) - if("Yes") - var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null - if(mins <= 0) - to_chat(usr, "[mins] is not a valid duration.") - return - var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null - if(!reason) - return - if(!DB_ban_record(BANTYPE_TEMP, M, mins, reason)) - to_chat(usr, "Failed to apply ban.") - return - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins) - ban_unban_log_save("[key_name(usr)] has banned [key_name(M)]. - Reason: [reason] - This will be removed in [mins] minutes.") - to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") - to_chat(M, "This is a temporary ban, it will be removed in [mins] minutes. The round ID is [GLOB.round_id].") - var/bran = CONFIG_GET(string/banappeals) - if(bran) - to_chat(M, "To try to resolve this matter head to [bran]") - else - to_chat(M, "No ban appeals URL has been set.") - log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [key_name(M)]\nThis will be removed in [mins] minutes.") - var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis will be removed in [mins] minutes." - message_admins(msg) - var/datum/admin_help/AH = M.client ? M.client.current_ticket : null - if(AH) - AH.Resolve() - qdel(M.client) - if("No") - var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null - if(!reason) - return - switch(alert(usr,"IP ban?",,"Yes","No","Cancel")) - if("Cancel") - return - if("Yes") - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) - if("No") - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0) - to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") - to_chat(M, "This is a permanent ban. The round ID is [GLOB.round_id].") - var/bran = CONFIG_GET(string/banappeals) - if(bran) - to_chat(M, "To try to resolve this matter head to [bran]") - else - to_chat(M, "No ban appeals URL has been set.") - if(!DB_ban_record(BANTYPE_PERMA, M, -1, reason)) - to_chat(usr, "Failed to apply ban.") - return - ban_unban_log_save("[key_name(usr)] has permabanned [key_name(M)]. - Reason: [reason] - This is a permanent ban.") - log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [reason]\nThis is a permanent ban.") - var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis is a permanent ban." - message_admins(msg) - var/datum/admin_help/AH = M.client ? M.client.current_ticket : null - if(AH) - AH.Resolve() - qdel(M.client) - if("Cancel") - return - - else if(href_list["mute"]) - if(!check_rights(R_ADMIN)) - return - cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) - - else if(href_list["c_mode"]) - return HandleCMode() - - else if(href_list["f_secret"]) - return HandleFSecret() - -//Dynamic mode - else if(href_list["f_dynamic_roundstart"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) - var/roundstart_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) - var/datum/dynamic_ruleset/roundstart/newrule = new rule() - roundstart_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in roundstart_rules - if (added_rule) - GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") - message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) - Game() - - else if(href_list["f_dynamic_roundstart_clear"]) - if(!check_rights(R_ADMIN)) - return - GLOB.dynamic_forced_roundstart_ruleset = list() - Game() - log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") - message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) - - else if(href_list["f_dynamic_roundstart_remove"]) - if(!check_rights(R_ADMIN)) - return - var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) - GLOB.dynamic_forced_roundstart_ruleset -= rule - Game() - log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") - message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) - - else if(href_list["f_dynamic_latejoin"]) - if(!check_rights(R_ADMIN)) - return - if(!SSticker || !SSticker.mode) - return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/latejoin_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin)) - var/datum/dynamic_ruleset/latejoin/newrule = new rule() - latejoin_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in latejoin_rules - if (added_rule) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.forced_latejoin_rule = latejoin_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") - message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1) - Game() - - else if(href_list["f_dynamic_latejoin_clear"]) - if(!check_rights(R_ADMIN)) - return - if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.forced_latejoin_rule = null - Game() - log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") - message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1) - - else if(href_list["f_dynamic_midround"]) - if(!check_rights(R_ADMIN)) - return - if(!SSticker || !SSticker.mode) - return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/midround_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/midround)) - var/datum/dynamic_ruleset/midround/newrule = new rule() - midround_rules[newrule.name] = rule - var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in midround_rules - if (added_rule) - var/datum/game_mode/dynamic/mode = SSticker.mode - log_admin("[key_name(usr)] executed the [added_rule] ruleset.") - message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1) - mode.picking_specific_rule(midround_rules[added_rule],1) - - else if (href_list["f_dynamic_options"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_centre"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num - if (new_centre < -5 || new_centre > 5) - return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") - message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) - GLOB.dynamic_curve_centre = new_centre - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_width"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num - if (new_width < 0.5 || new_width > 4) - return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") - message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) - GLOB.dynamic_curve_width = new_width - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_midround_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_midround_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_force_extended"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended - log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_no_stacking"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_classic_secret"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret - log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_stacking_limit"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num - log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_high_pop_limit"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num - if (new_value < 0) - return alert(usr, "Only positive values allowed!", null, null, null, null) - GLOB.dynamic_high_pop_limit = new_value - - log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_forced_threat"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num - if (new_value > 100) - return alert(usr, "The value must be be under 100.", null, null, null, null) - GLOB.dynamic_forced_threat_level = new_value - - log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_chaos_level"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the chaos level for dynamic mode.", "Chaos level") as num - if (new_value > 5 || new_value < 0) - return alert(usr, "The value must be between 0 and 5.", null, null, null, null) - GLOB.dynamic_chaos_level = new_value - - log_admin("[key_name(usr)] set 'dynamic_chaos_level' to [GLOB.dynamic_chaos_level].") - message_admins("[key_name(usr)] set 'dynamic_chaos_level' to [GLOB.dynamic_chaos_level].") - dynamic_mode_options(usr) -//End Dynamic mode - - else if(href_list["c_mode2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if (SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - GLOB.master_mode = href_list["c_mode2"] - log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") - message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") - to_chat(world, "The mode is now: [GLOB.master_mode]") - Game() // updates the main game menu - SSticker.save_mode(GLOB.master_mode) - HandleCMode() - - else if(href_list["f_secret2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - GLOB.secret_force_mode = href_list["f_secret2"] - log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - Game() // updates the main game menu - HandleFSecret() - - else if(href_list["monkeyone"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - 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)].") - H.monkeyize() - - else if(href_list["humanone"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/monkey/Mo = locate(href_list["humanone"]) - if(!istype(Mo)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/monkey.") - return - - log_admin("[key_name(usr)] attempting to humanize [key_name(Mo)].") - message_admins("[key_name_admin(usr)] attempting to humanize [key_name_admin(Mo)].") - Mo.humanize() - - else if(href_list["corgione"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["corgione"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - 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)].") - H.corgize() - - - else if(href_list["forcespeech"]) - if(!check_rights(R_FUN)) - return - - var/mob/M = locate(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, forced = "admin 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["makeeligible"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["makeeligible"]) - if(!ismob(M)) - to_chat(usr, "this can only be used on instances of type /mob.") - if(M.ckey in GLOB.client_ghost_timeouts) - GLOB.client_ghost_timeouts -= M.ckey - - else if(href_list["sendtoprison"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendtoprison"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - return - - if(alert(usr, "Send [key_name(M)] to Prison?", "Message", "Yes", "No") != "Yes") - return - - M.forceMove(pick(GLOB.prisonwarp)) - to_chat(M, "You have been sent to Prison!") - - log_admin("[key_name(usr)] has sent [key_name(M)] to Prison!") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to Prison!") - - else if(href_list["sendbacktolobby"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(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(usr)] has sent [key_name(M)] back to the Lobby.") - - var/mob/dead/new_player/NP = new() - NP.ckey = M.ckey - qdel(M) - - else if(href_list["tdome1"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdome1"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdome1)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 1)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 1)") - - else if(href_list["tdome2"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdome2"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdome2)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 2)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 2)") - - else if(href_list["tdomeadmin"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdomeadmin"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - return - var/mob/living/L = M - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeadmin)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Admin.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Admin.)") - - else if(href_list["tdomeobserve"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdomeobserve"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - if(ishuman(L)) - var/mob/living/carbon/human/observer = L - 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/sneakers/black(observer), SLOT_SHOES) - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeobserve)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Observer.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Observer.)") - - else if(href_list["revive"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/living/L = locate(href_list["revive"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /mob/living.") - return - - L.revive(full_heal = 1, admin_revive = 1) - message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!") - 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 = locate(href_list["makeai"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - - message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") - log_admin("[key_name(usr)] AIized [key_name(H)].") - H.AIize() - - else if(href_list["makealien"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makealien"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - - usr.client.cmd_admin_alienize(H) - - else if(href_list["makeslime"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeslime"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - - usr.client.cmd_admin_slimeize(H) - - else if(href_list["makeblob"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeblob"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - - usr.client.cmd_admin_blobize(H) - - - else if(href_list["makerobot"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makerobot"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - - usr.client.cmd_admin_robotize(H) - - else if(href_list["makeanimal"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/M = locate(href_list["makeanimal"]) - if(isnewplayer(M)) - to_chat(usr, "This cannot be used on instances of type /mob/dead/new_player.") - return - - usr.client.cmd_admin_animalize(M) - - else if(href_list["adminplayeropts"]) - var/mob/M = locate(href_list["adminplayeropts"]) - show_player_panel(M) - - else if(href_list["adminplayerobservefollow"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/atom/movable/AM = locate(href_list["adminplayerobservefollow"]) - - var/client/C = usr.client - if(!isobserver(usr)) - C.admin_ghost() - var/mob/dead/observer/A = C.mob - A.ManualFollow(AM) - - else if(href_list["admingetmovable"]) - if(!check_rights(R_ADMIN)) - return - - var/atom/movable/AM = locate(href_list["admingetmovable"]) - if(QDELETED(AM)) - return - AM.forceMove(get_turf(usr)) - - else if(href_list["adminplayerobservecoodjump"]) - if(!isobserver(usr) && !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"]) - if(!check_rights(R_ADMIN)) - return - output_ai_laws() - - else if(href_list["admincheckdevilinfo"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["admincheckdevilinfo"]) - output_devil_info(M) - - else if(href_list["adminmoreinfo"]) - var/mob/M = locate(href_list["adminmoreinfo"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.") - return - - var/location_description = "" - var/special_role_description = "" - var/health_description = "" - var/gender_description = "" - var/turf/T = get_turf(M) - - //Location - if(isturf(T)) - if(isarea(T.loc)) - location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z] in area [T.loc])" - else - location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z])" - - //Job + antagonist - if(M.mind) - special_role_description = "Role: [M.mind.assigned_role]; Antagonist: [M.mind.special_role]" - else - special_role_description = "Role: Mind datum missing Antagonist: Mind datum missing" - - //Health - if(isliving(M)) - var/mob/living/L = M - var/status - switch (M.stat) - if(CONSCIOUS) - status = "Alive" - if(SOFT_CRIT) - status = "Dying" - if(UNCONSCIOUS) - status = "[L.InCritical() ? "Unconscious and Dying" : "Unconscious"]" - if(DEAD) - status = "Dead" - health_description = "Status = [status]" - health_description += "
    Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getOrganLoss(ORGAN_SLOT_BRAIN)] - Stamina: [L.getStaminaLoss()]" - else - health_description = "This mob type has no health to speak of." - - //Gender - switch(M.gender) - if(MALE,FEMALE) - gender_description = "[M.gender]" - else - gender_description = "[M.gender]" - - to_chat(src.owner, "Info about [M.name]: ") - to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]") - to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];") - to_chat(src.owner, "Location = [location_description];") - to_chat(src.owner, "[special_role_description]") - to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M)) - - else if(href_list["addjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Add = href_list["addjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Add) - job.total_positions += 1 - break - - src.manage_free_slots() - - - else if(href_list["customjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Add = href_list["customjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Add) - var/newtime = null - newtime = input(usr, "How many jebs do you want?", "Add wanted posters", "[newtime]") as num|null - if(!newtime) - to_chat(src.owner, "Setting to amount of positions filled for the job") - job.total_positions = job.current_positions - break - job.total_positions = newtime - - src.manage_free_slots() - - else if(href_list["removejobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Remove = href_list["removejobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Remove && job.total_positions - job.current_positions > 0) - job.total_positions -= 1 - break - - src.manage_free_slots() - - else if(href_list["unlimitjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Unlimit = href_list["unlimitjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Unlimit) - job.total_positions = -1 - break - - src.manage_free_slots() - - else if(href_list["limitjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Limit = href_list["limitjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Limit) - job.total_positions = job.current_positions - break - - src.manage_free_slots() - - - else if(href_list["adminspawncookie"]) - if(!check_rights(R_ADMIN|R_FUN)) - return - - var/mob/living/carbon/human/H = locate(href_list["adminspawncookie"]) - if(!ishuman(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - - var/obj/item/reagent_containers/food/snacks/cookie/cookie = new(H) - if(H.put_in_hands(cookie)) - H.update_inv_hands() - else - qdel(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(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") - return - - log_admin("[key_name(H)] got their cookie, spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] got their cookie, spawned by [key_name(src.owner)].") - SSblackbox.record_feedback("amount", "admin_cookies_spawned", 1) - to_chat(H, "Your prayers have been answered!! You received the best cookie!") - SEND_SOUND(H, sound('sound/effects/pray_chaplain.ogg')) - - else if(href_list["adminsmite"]) - if(!check_rights(R_ADMIN|R_FUN)) - return - - var/mob/living/carbon/human/H = locate(href_list["adminsmite"]) in GLOB.mob_list - if(!H || !istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - - usr.client.smite(H) - - else if(href_list["CentComReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["CentComReply"]) - usr.client.admin_headset_message(M, RADIO_CHANNEL_CENTCOM) - - else if(href_list["SyndicateReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["SyndicateReply"]) - usr.client.admin_headset_message(M, RADIO_CHANNEL_SYNDICATE) - - else if(href_list["HeadsetMessage"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["HeadsetMessage"]) - usr.client.admin_headset_message(M) - - else if(href_list["reject_custom_name"]) - if(!check_rights(R_ADMIN)) - return - var/obj/item/station_charter/charter = locate(href_list["reject_custom_name"]) - if(istype(charter)) - charter.reject_proposed(usr) - else if(href_list["jumpto"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/mob/M = locate(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 = locate(href_list["getmob"]) - usr.client.Getmob(M) - - else if(href_list["sendmob"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendmob"]) - usr.client.sendmob(M) - - else if(href_list["narrateto"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["narrateto"]) - usr.client.cmd_admin_direct_narrate(M) - - else if(href_list["subtlemessage"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["subtlemessage"]) - usr.client.cmd_admin_subtle_message(M) - - else if(href_list["individuallog"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["individuallog"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.") - return - - show_individual_logging_panel(M, href_list["log_src"], href_list["log_type"]) - else if(href_list["languagemenu"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["languagemenu"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.") - return - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) - - else if(href_list["traitor"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - return - - var/mob/M = locate(href_list["traitor"]) - if(!ismob(M)) - var/datum/mind/D = M - if(!istype(D)) - to_chat(usr, "This can only be used on instances of type /mob and /mind") - return - else - D.traitor_panel() - else - show_traitor_panel(M) - - else if(href_list["borgpanel"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["borgpanel"]) - if(!iscyborg(M)) - to_chat(usr, "This can only be used on cyborgs") - else - open_borgopanel(M) - - else if(href_list["initmind"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["initmind"]) - if(!ismob(M) || M.mind) - to_chat(usr, "This can only be used on instances on mindless mobs") - return - M.mind_initialize() - - 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["dupe_marked_datum"]) - if(!check_rights(R_SPAWN)) - return - return DuplicateObject(marked_datum, perfectcopy=1, newloc=get_turf(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 = CLAMP(text2num(href_list["object_count"]), 1, 100) - 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/obj_dir = text2num(href_list["object_dir"]) - if(obj_dir && !(obj_dir in list(1,2,4,8,5,6,9,10))) - obj_dir = null - 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) && !iscyborg(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(!QDELETED(O)) - O.flags_1 |= ADMIN_SPAWNED_1 - if(obj_dir) - O.setDir(obj_dir) - if(obj_name) - O.name = obj_name - if(ismob(O)) - var/mob/M = O - M.real_name = obj_name - if(where == "inhand" && isliving(usr) && isitem(O)) - var/mob/living/L = usr - var/obj/item/I = O - L.put_in_hands(I) - if(iscyborg(L)) - var/mob/living/silicon/robot/R = L - if(R.module) - R.module.add_module(I, TRUE, TRUE) - R.activate_module(I) - - - 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["secrets"]) - Secrets_topic(href_list["secrets"],href_list) - - else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here - if(!check_rights(R_ADMIN)) - return - 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"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_channel.channel_name = stripped_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"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked - src.access_news_network() - - else if(href_list["ac_submit_new_channel"]) - if(!check_rights(R_ADMIN)) - return - var/check = 0 - for(var/datum/newscaster/feed_channel/FC in GLOB.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") - GLOB.news_network.CreateFeedChannel(src.admincaster_feed_channel.channel_name, src.admin_signature, src.admincaster_feed_channel.locked, 1) - SSblackbox.record_feedback("tally", "newscaster_channels", 1, src.admincaster_feed_channel.channel_name) - log_admin("[key_name(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"]) - if(!check_rights(R_ADMIN)) - return - var/list/available_channels = list() - for(var/datum/newscaster/feed_channel/F in GLOB.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"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_message.body = adminscrub(input(usr, "Write your Feed story.", "Network Channel Handler", "")) - while (findtext(src.admincaster_feed_message.returnBody(-1)," ") == 1) - src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.returnBody(-1),2,length(src.admincaster_feed_message.returnBody(-1))+1) - src.access_news_network() - - else if(href_list["ac_submit_new_message"]) - if(!check_rights(R_ADMIN)) - return - if(src.admincaster_feed_message.returnBody(-1) =="" || src.admincaster_feed_message.returnBody(-1) =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) - src.admincaster_screen = 6 - else - GLOB.news_network.SubmitArticle(src.admincaster_feed_message.returnBody(-1), src.admin_signature, src.admincaster_feed_channel.channel_name, null, 1) - SSblackbox.record_feedback("amount", "newscaster_stories", 1) - src.admincaster_screen=4 - - for(var/obj/machinery/newscaster/NEWSCASTER in GLOB.allCasters) - NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) - - log_admin("[key_name(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") - src.access_news_network() - - else if(href_list["ac_create_channel"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=2 - src.access_news_network() - - else if(href_list["ac_create_feed_story"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=3 - src.access_news_network() - - else if(href_list["ac_menu_censor_story"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=10 - src.access_news_network() - - else if(href_list["ac_menu_censor_channel"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=11 - src.access_news_network() - - else if(href_list["ac_menu_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/already_wanted = 0 - if(GLOB.news_network.wanted_issue.active) - already_wanted = 1 - - if(already_wanted) - src.admincaster_wanted_message.criminal = GLOB.news_network.wanted_issue.criminal - src.admincaster_wanted_message.body = GLOB.news_network.wanted_issue.body - src.admincaster_screen = 14 - src.access_news_network() - - else if(href_list["ac_set_wanted_name"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_wanted_message.criminal = adminscrub(input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) - while(findtext(src.admincaster_wanted_message.criminal," ") == 1) - src.admincaster_wanted_message.criminal = copytext(admincaster_wanted_message.criminal,2,length(admincaster_wanted_message.criminal)+1) - src.access_news_network() - - else if(href_list["ac_set_wanted_desc"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_wanted_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_wanted_message.body," ") == 1) - src.admincaster_wanted_message.body = copytext(src.admincaster_wanted_message.body,2,length(src.admincaster_wanted_message.body)+1) - src.access_news_network() - - else if(href_list["ac_submit_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/input_param = text2num(href_list["ac_submit_wanted"]) - if(src.admincaster_wanted_message.criminal == "" || src.admincaster_wanted_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 - GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature, null, 1, 1) - src.admincaster_screen = 15 - else - GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature) - src.admincaster_screen = 19 - log_admin("[key_name(usr)] issued a Station-wide Wanted Notification for [src.admincaster_wanted_message.criminal]!") - src.access_news_network() - - else if(href_list["ac_cancel_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/choice = alert("Please confirm Wanted Issue removal.","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - GLOB.news_network.deleteWanted() - src.admincaster_screen=17 - src.access_news_network() - - else if(href_list["ac_censor_channel_author"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) - FC.toggleCensorAuthor() - src.access_news_network() - - else if(href_list["ac_censor_channel_story_author"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) - MSG.toggleCensorAuthor() - src.access_news_network() - - else if(href_list["ac_censor_channel_story_body"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) - MSG.toggleCensorBody() - src.access_news_network() - - else if(href_list["ac_pick_d_notice"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/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"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) - FC.toggleCensorDclass() - src.access_news_network() - - else if(href_list["ac_view"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=1 - src.access_news_network() - - else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen = text2num(href_list["ac_setScreen"]) - if (src.admincaster_screen == 0) - if(src.admincaster_feed_channel) - src.admincaster_feed_channel = new /datum/newscaster/feed_channel - if(src.admincaster_feed_message) - src.admincaster_feed_message = new /datum/newscaster/feed_message - if(admincaster_wanted_message) - admincaster_wanted_message = new /datum/newscaster/wanted_message - src.access_news_network() - - else if(href_list["ac_show_channel"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/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"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/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"]) - if(!check_rights(R_ADMIN)) - return - src.access_news_network() - - else if(href_list["ac_set_signature"]) - if(!check_rights(R_ADMIN)) - return - src.admin_signature = adminscrub(input(usr, "Provide your desired signature.", "Network Identity Handler", "")) - src.access_news_network() - - else if(href_list["ac_del_comment"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_comment/FC = locate(href_list["ac_del_comment"]) - var/datum/newscaster/feed_message/FM = locate(href_list["ac_del_comment_msg"]) - FM.comments -= FC - qdel(FC) - src.access_news_network() - - else if(href_list["ac_lock_comment"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/FM = locate(href_list["ac_lock_comment"]) - FM.locked ^= 1 - src.access_news_network() - - else if(href_list["check_antagonist"]) - if(!check_rights(R_ADMIN)) - return - usr.client.check_antagonists() - - else if(href_list["kick_all_from_lobby"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker.IsRoundInProgress()) - var/afkonly = text2num(href_list["afkonly"]) - if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby??","Message","Yes","Cancel") != "Yes") - to_chat(usr, "Kick clients from lobby aborted") - return - var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", 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["create_outfit"]) - if(!check_rights(R_ADMIN)) - return - - var/datum/outfit/O = new /datum/outfit - //swap this for js dropdowns sometime - O.name = href_list["outfit_name"] - O.uniform = text2path(href_list["outfit_uniform"]) - O.shoes = text2path(href_list["outfit_shoes"]) - O.gloves = text2path(href_list["outfit_gloves"]) - O.suit = text2path(href_list["outfit_suit"]) - O.head = text2path(href_list["outfit_head"]) - O.back = text2path(href_list["outfit_back"]) - O.mask = text2path(href_list["outfit_mask"]) - O.glasses = text2path(href_list["outfit_glasses"]) - O.r_hand = text2path(href_list["outfit_r_hand"]) - O.l_hand = text2path(href_list["outfit_l_hand"]) - O.suit_store = text2path(href_list["outfit_s_store"]) - O.l_pocket = text2path(href_list["outfit_l_pocket"]) - O.r_pocket = text2path(href_list["outfit_r_pocket"]) - O.id = text2path(href_list["outfit_id"]) - O.belt = text2path(href_list["outfit_belt"]) - O.ears = text2path(href_list["outfit_ears"]) - - GLOB.custom_outfits.Add(O) - message_admins("[key_name(usr)] created \"[O.name]\" outfit!") - - else if(href_list["set_selfdestruct_code"]) - if(!check_rights(R_ADMIN)) - return - var/code = random_nukecode() - for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) - SD.r_code = code - message_admins("[key_name_admin(usr)] has set the self-destruct \ - code to \"[code]\".") - - else if(href_list["add_station_goal"]) - if(!check_rights(R_ADMIN)) - 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 = input("Enter goal name:") as text|null - if(!newname) - return - G.name = newname - var/description = input("Enter CentCom message contents:") as message|null - if(!description) - return - G.report_message = description - message_admins("[key_name(usr)] created \"[G.name]\" station goal.") - SSticker.mode.station_goals += G - modify_goals() - - else if(href_list["viewruntime"]) - var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) - if(!istype(error_viewer)) - to_chat(usr, "That runtime viewer no longer exists.") - return - - if(href_list["viewruntime_backto"]) - error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) - else - error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) - - else if(href_list["showrelatedacc"]) - if(!check_rights(R_ADMIN)) - return - var/client/C = locate(href_list["client"]) in GLOB.clients - var/thing_to_check - if(href_list["showrelatedacc"] == "cid") - thing_to_check = C.related_accounts_cid - else - thing_to_check = C.related_accounts_ip - thing_to_check = splittext(thing_to_check, ", ") - - - var/list/dat = list("Related accounts by [uppertext(href_list["showrelatedacc"])]:") - dat += thing_to_check - - usr << browse(dat.Join("
    "), "window=related_[C];size=420x300") - else if(href_list["centcomlookup"]) - if(!check_rights(R_ADMIN)) - return - - if(!CONFIG_GET(string/centcom_ban_db)) - to_chat(usr, "Centcom Galactic Ban DB is disabled!") - return - - var/ckey = href_list["centcomlookup"] - - // Make the request - var/datum/http_request/request = new() - request.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/centcom_ban_db)]/[ckey]", "", "") - request.begin_async() - UNTIL(request.is_complete() || !usr) - if (!usr) - return - var/datum/http_response/response = request.into_response() - - var/list/bans - - var/list/dat = list("") - - if(response.errored) - dat += "
    Failed to connect to CentCom." - else if(response.status_code != 200) - dat += "
    Failed to connect to CentCom. Status code: [response.status_code]" - else - if(response.body == "[]") - dat += "
    0 bans detected for [ckey]
    " - else - bans = json_decode(response["body"]) - dat += "
    [bans.len] ban\s detected for [ckey]
    " - for(var/list/ban in bans) - dat += "Server: [sanitize(ban["sourceName"])]
    " - dat += "RP Level: [sanitize(ban["sourceRoleplayLevel"])]
    " - dat += "Type: [sanitize(ban["type"])]
    " - dat += "Banned By: [sanitize(ban["bannedBy"])]
    " - dat += "Reason: [sanitize(ban["reason"])]
    " - dat += "Datetime: [sanitize(ban["bannedOn"])]
    " - var/expiration = ban["expires"] - dat += "Expires: [expiration ? "[sanitize(expiration)]" : "Permanent"]
    " - if(ban["type"] == "job") - dat += "Jobs: " - var/list/jobs = ban["jobs"] - dat += sanitize(jobs.Join(", ")) - dat += "
    " - dat += "
    " - - dat += "
    " - var/datum/browser/popup = new(usr, "centcomlookup-[ckey]", "
    Central Command Galactic Ban Database
    ", 700, 600) - popup.set_content(dat.Join()) - popup.open(0) - - - else if(href_list["modantagrep"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["mob"]) in GLOB.mob_list - var/client/C = M.client - usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) - show_player_panel(M) - - else if(href_list["slowquery"]) - if(!check_rights(R_ADMIN)) - return - var/answer = href_list["slowquery"] - if(answer == "yes") - log_query_debug("[usr.key] | Reported a server hang") - if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") - var/response = input(usr,"What were you just doing?","Query server hang report") as null|text - if(response) - log_query_debug("[usr.key] | [response]") - else if(answer == "no") - log_query_debug("[usr.key] | Reported no server hang") - -/datum/admins/proc/HandleCMode() - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - 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: [GLOB.master_mode]"} - usr << browse(dat, "window=c_mode") - -/datum/admins/proc/HandleFSecret() - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.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: [GLOB.secret_force_mode]"} - usr << browse(dat, "window=f_secret") - -/datum/admins/proc/makeMentor(ckey) - if(!usr.client) - return - if (!check_rights(0)) - return - if(!ckey) - return - var/client/C = GLOB.directory[ckey] - if(C) - if(check_rights_for(C, R_ADMIN,0)) - to_chat(usr, "The client chosen is an admin! Cannot mentorize.") - return - if(SSdbcore.Connect()) - var/datum/DBQuery/query_get_mentor = SSdbcore.NewQuery("SELECT id FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") - if(query_get_mentor.NextRow()) - to_chat(usr, "[ckey] is already a mentor.") - return - var/datum/DBQuery/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") - if(!query_add_mentor.warn_execute()) - return - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") - if(!query_add_admin_log.warn_execute()) - return - else - to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") - new /datum/mentors(ckey) - to_chat(usr, "New mentor added.") - -/datum/admins/proc/removeMentor(ckey) - if(!usr.client) - return - if (!check_rights(0)) - return - if(!ckey) - return - var/client/C = GLOB.directory[ckey] - if(C) - if(check_rights_for(C, R_ADMIN,0)) - to_chat(usr, "The client chosen is an admin, not a mentor! Cannot de-mentorize.") - return - C.remove_mentor_verbs() - C.mentor_datum = null - GLOB.mentors -= C - if(SSdbcore.Connect()) - var/datum/DBQuery/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") - if(!query_remove_mentor.warn_execute()) - return - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed mentor [ckey]');") - if(!query_add_admin_log.warn_execute()) - return - else - to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") - to_chat(usr, "Mentor removed.") +/datum/admins/proc/CheckAdminHref(href, href_list) + var/auth = href_list["admin_token"] + . = auth && (auth == href_token || auth == GLOB.href_token) + if(.) + return + var/msg = !auth ? "no" : "a bad" + message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") + if(CONFIG_GET(flag/debug_admin_hrefs)) + message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") + log_world("UAH: [href]") + return TRUE + log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") + +/datum/admins/Topic(href, href_list) + ..() + + if(usr.client != src.owner || !check_rights(0)) + message_admins("[usr.key] tried to use the admin panel without authorization.") + log_admin("[key_name(usr)] tried to use the admin panel without authorization.") + return + + if(!CheckAdminHref(href, href_list)) + return + + if(href_list["makementor"]) + makeMentor(href_list["makementor"]) + else if(href_list["removementor"]) + removeMentor(href_list["removementor"]) + + if(href_list["ahelp"]) + if(!check_rights(R_ADMIN, TRUE)) + return + + var/ahelp_ref = href_list["ahelp"] + var/datum/admin_help/AH = locate(ahelp_ref) + if(AH) + AH.Action(href_list["ahelp_action"]) + else + to_chat(usr, "Ticket [ahelp_ref] has been deleted!") + + else if(href_list["ahelp_tickets"]) + GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"])) + + else if(href_list["stickyban"]) + stickyban(href_list["stickyban"],href_list) + + else if(href_list["getplaytimewindow"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["getplaytimewindow"]) in GLOB.mob_list + if(!M) + to_chat(usr, "ERROR: Mob not found.") + return + cmd_show_exp_panel(M.client) + + else if(href_list["toggleexempt"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["toggleexempt"]) in GLOB.clients + if(!C) + to_chat(usr, "ERROR: Client not found.") + return + toggle_exempt_status(C) + + else if(href_list["makeAntag"]) + if(!check_rights(R_ADMIN)) + return + if (!SSticker.mode) + to_chat(usr, "Not until the round starts!") + return + switch(href_list["makeAntag"]) + if("traitors") + if(src.makeTraitors()) + message_admins("[key_name_admin(usr)] created traitors.") + log_admin("[key_name(usr)] created traitors.") + else + message_admins("[key_name_admin(usr)] tried to create traitors. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create traitors.") + if("changelings") + if(src.makeChangelings()) + message_admins("[key_name(usr)] created changelings.") + log_admin("[key_name(usr)] created changelings.") + else + message_admins("[key_name_admin(usr)] tried to create changelings. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create changelings.") + if("revs") + if(src.makeRevs()) + message_admins("[key_name(usr)] started a revolution.") + log_admin("[key_name(usr)] started a revolution.") + else + message_admins("[key_name_admin(usr)] tried to start a revolution. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a revolution.") + if("cult") + if(src.makeCult()) + message_admins("[key_name(usr)] started a cult.") + log_admin("[key_name(usr)] started a cult.") + else + message_admins("[key_name_admin(usr)] tried to start a cult. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a cult.") + if("wizard") + message_admins("[key_name(usr)] is creating a wizard...") + if(src.makeWizard()) + message_admins("[key_name(usr)] created a wizard.") + log_admin("[key_name(usr)] created a wizard.") + else + message_admins("[key_name_admin(usr)] tried to create a wizard. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a wizard.") + if("nukeops") + message_admins("[key_name(usr)] is creating a nuke team...") + if(src.makeNukeTeam()) + message_admins("[key_name(usr)] created a nuke team.") + log_admin("[key_name(usr)] created a nuke team.") + else + message_admins("[key_name_admin(usr)] tried to create a nuke team. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a nuke team.") + if("ninja") + message_admins("[key_name(usr)] spawned a ninja.") + log_admin("[key_name(usr)] spawned a ninja.") + src.makeSpaceNinja() + if("aliens") + message_admins("[key_name(usr)] started an alien infestation.") + log_admin("[key_name(usr)] started an alien infestation.") + src.makeAliens() + if("deathsquad") + message_admins("[key_name(usr)] is creating a death squad...") + if(src.makeDeathsquad()) + message_admins("[key_name(usr)] created a death squad.") + log_admin("[key_name(usr)] created a death squad.") + else + message_admins("[key_name_admin(usr)] tried to create a death squad. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a death squad.") + if("blob") + var/strength = input("Set Blob Resource Gain Rate","Set Resource Rate",1) as num|null + if(!strength) + return + message_admins("[key_name(usr)] spawned a blob with base resource gain [strength].") + log_admin("[key_name(usr)] spawned a blob with base resource gain [strength].") + new/datum/round_event/ghost_role/blob(TRUE, strength) + if("centcom") + message_admins("[key_name(usr)] is creating a CentCom response team...") + if(src.makeEmergencyresponseteam()) + message_admins("[key_name(usr)] created a CentCom response team.") + log_admin("[key_name(usr)] created a CentCom response team.") + else + message_admins("[key_name_admin(usr)] tried to create a CentCom response team. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a CentCom response team.") + if("abductors") + message_admins("[key_name(usr)] is creating an abductor team...") + if(src.makeAbductorTeam()) + message_admins("[key_name(usr)] created an abductor team.") + log_admin("[key_name(usr)] created an abductor team.") + else + message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunatly there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create an abductor team.") + if("clockcult") + if(src.makeClockCult()) + message_admins("[key_name(usr)] started a clockwork cult.") + log_admin("[key_name(usr)] started a clockwork cult.") + else + message_admins("[key_name_admin(usr)] tried to start a clockwork cult. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a clockwork cult.") + if("revenant") + if(src.makeRevenant()) + message_admins("[key_name(usr)] created a revenant.") + log_admin("[key_name(usr)] created a revenant.") + else + message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a revenant.") + //Hyper + if("lewdtraitors") + if(src.makeLewdtraitors()) + message_admins("[key_name(usr)] created a lewd traitor.") + log_admin("[key_name(usr)] created a lewd traitor.") + else + message_admins("[key_name_admin(usr)] tried to create a lewd traitor. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a lewd traitor.") + + else if(href_list["forceevent"]) + if(!check_rights(R_FUN)) + return + var/datum/round_event_control/E = locate(href_list["forceevent"]) in SSevents.control + if(E) + E.admin_setup(usr) + var/datum/round_event/event = E.runEvent() + if(event.announceWhen>0) + event.processing = FALSE + var/prompt = alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel") + switch(prompt) + if("Cancel") + event.kill() + return + if("No") + event.announceWhen = -1 + event.processing = TRUE + message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])") + log_admin("[key_name(usr)] has triggered an event. ([E.name])") + return + + else if(href_list["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"]) + var/adminckey = href_list["dbsearchadmin"] + var/playerckey = href_list["dbsearchckey"] + var/ip = href_list["dbsearchip"] + var/cid = href_list["dbsearchcid"] + var/page = href_list["dbsearchpage"] + + DB_ban_panel(playerckey, adminckey, ip, cid, page) + 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"]) + if(!check_rights(R_BAN)) + return + var/bantype = text2num(href_list["dbbanaddtype"]) + var/bankey = href_list["dbbanaddkey"] + var/banckey = ckey(bankey) + 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"] + var/banseverity = href_list["dbbanaddseverity"] + + switch(bantype) + if(BANTYPE_PERMA) + if(!banckey || !banreason || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, and reason).") + return + banduration = null + banjob = null + if(BANTYPE_TEMP) + if(!banckey || !banreason || !banduration || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, reason, severity and duration).") + return + banjob = null + if(BANTYPE_JOB_PERMA) + if(!banckey || !banreason || !banjob || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") + return + banduration = null + if(BANTYPE_JOB_TEMP) + if(!banckey || !banreason || !banjob || !banduration || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") + return + if(BANTYPE_ADMIN_PERMA) + if(!banckey || !banreason || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity and reason).") + return + banduration = null + banjob = null + if(BANTYPE_ADMIN_TEMP) + if(!banckey || !banreason || !banduration || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, 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.key] 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.") + + if(!DB_ban_record(bantype, playermob, banduration, banreason, banjob, bankey, banip, bancid )) + to_chat(usr, "Failed to apply ban.") + return + create_message("note", bankey, null, banreason, null, null, 0, 0, null, 0, banseverity) + + else if(href_list["editrightsbrowser"]) + edit_admin_permissions(0) + + else if(href_list["editrightsbrowserlog"]) + edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"]) + + if(href_list["editrightsbrowsermanage"]) + if(href_list["editrightschange"]) + change_admin_rank(ckey(href_list["editrightschange"]), href_list["editrightschange"], TRUE) + else if(href_list["editrightsremove"]) + remove_admin(ckey(href_list["editrightsremove"]), href_list["editrightsremove"], TRUE) + else if(href_list["editrightsremoverank"]) + remove_rank(href_list["editrightsremoverank"]) + edit_admin_permissions(2) + + else if(href_list["editrights"]) + edit_rights_topic(href_list) + + else if(href_list["gamemode_panel"]) + if(!check_rights(R_ADMIN)) + return + SSticker.mode.admin_panel() + + + else if(href_list["call_shuttle"]) + if(!check_rights(R_ADMIN)) + return + + + switch(href_list["call_shuttle"]) + if("1") + if(EMERGENCY_AT_LEAST_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(EMERGENCY_AT_LEAST_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|null + if(!timer) + return + SSshuttle.emergency.setTimer(timer*10) + log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") + minor_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["trigger_centcom_recall"]) + if(!check_rights(R_ADMIN)) + return + + usr.client.trigger_centcom_recall() + + else if(href_list["toggle_continuous"]) + if(!check_rights(R_ADMIN)) + return + var/list/continuous = CONFIG_GET(keyed_list/continuous) + if(!continuous[SSticker.mode.config_tag]) + continuous[SSticker.mode.config_tag] = TRUE + else + continuous[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") + check_antagonists() + + else if(href_list["toggle_midround_antag"]) + if(!check_rights(R_ADMIN)) + return + + var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) + if(!midround_antag[SSticker.mode.config_tag]) + midround_antag[SSticker.mode.config_tag] = TRUE + else + midround_antag[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") + check_antagonists() + + else if(href_list["alter_midround_time_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null + if(!timer) + return + CONFIG_SET(number/midround_antag_time_check, timer) + message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") + check_antagonists() + + else if(href_list["alter_midround_life_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num + if(!ratio) + return + CONFIG_SET(number/midround_antag_life_check, ratio / 100) + + message_admins("[key_name_admin(usr)] edited the midround antagonist living crew ratio to [ratio]% alive.") + check_antagonists() + + else if(href_list["toggle_noncontinuous_behavior"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.mode.round_ends_with_antag_death) + SSticker.mode.round_ends_with_antag_death = 1 + else + SSticker.mode.round_ends_with_antag_death = 0 + + message_admins("[key_name_admin(usr)] edited the midround antagonist system to [SSticker.mode.round_ends_with_antag_death ? "end the round" : "continue as extended"] upon failure.") + check_antagonists() + + else if(href_list["delay_round_end"]) + if(!check_rights(R_SERVER)) + return + if(!SSticker.delay_end) + SSticker.admin_delay_notice = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text + if(isnull(SSticker.admin_delay_notice)) + return + else + SSticker.admin_delay_notice = null + SSticker.delay_end = !SSticker.delay_end + var/reason = SSticker.delay_end ? "for reason: [SSticker.admin_delay_notice]" : "."//laziness + var/msg = "[SSticker.delay_end ? "delayed" : "undelayed"] the round end [reason]" + log_admin("[key_name(usr)] [msg]") + message_admins("[key_name_admin(usr)] [msg]") + href_list["secrets"] = "check_antagonist" + if(SSticker.ready_for_reboot && !SSticker.delay_end) //we undelayed after standard reboot would occur + SSticker.standard_reboot() + + else if(href_list["end_round"]) + if(!check_rights(R_ADMIN)) + return + + message_admins("[key_name_admin(usr)] is considering ending the round.") + if(alert(usr, "This will end the round, are you SURE you want to do this?", "Confirmation", "Yes", "No") == "Yes") + if(alert(usr, "Final Confirmation: End the round NOW?", "Confirmation", "Yes", "No") == "Yes") + message_admins("[key_name_admin(usr)] has ended the round.") + SSticker.force_ending = 1 //Yeah there we go APC destroyed mission accomplished + return + else + message_admins("[key_name_admin(usr)] decided against ending the round.") + else + message_admins("[key_name_admin(usr)] decided against ending the round.") + + else if(href_list["simplemake"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/M = locate(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 + + 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]") + switch(href_list["simplemake"]) + if("observer") + M.change_mob_type( /mob/dead/observer , null, null, delmob ) + if("drone") + M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) + if("hunter") + M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) + if("queen") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) + if("praetorian") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) + if("sentinel") + M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) + if("larva") + M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) + 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 ) + if(posttransformoutfit && istype(newmob)) + newmob.equipOutfit(posttransformoutfit) + if("slime") + M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) + if("monkey") + M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) + if("robot") + M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) + if("cat") + M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) + if("runtime") + M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob ) + if("corgi") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) + if("ian") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/Ian , null, null, delmob ) + if("pug") + M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) + if("crab") + M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) + if("coffee") + M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob ) + if("parrot") + M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) + if("polyparrot") + M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob ) + if("constructarmored") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/armored , null, null, delmob ) + if("constructbuilder") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/builder , null, null, delmob ) + if("constructwraith") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob ) + if("shade") + M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob ) + + + /////////////////////////////////////new ban stuff + else if(href_list["unbanf"]) + if(!check_rights(R_BAN)) + return + + var/banfolder = href_list["unbanf"] + GLOB.Banlist.cd = "/base/[banfolder]" + var/key = GLOB.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["unbane"]) + if(!check_rights(R_BAN)) + return + + UpdateTime() + var/reason + + var/banfolder = href_list["unbane"] + GLOB.Banlist.cd = "/base/[banfolder]" + var/reason2 = GLOB.Banlist["reason"] + var/temp = GLOB.Banlist["temp"] + + var/minutes = GLOB.Banlist["minutes"] + + var/banned_key = GLOB.Banlist["key"] + GLOB.Banlist.cd = "/base" + + var/duration + + switch(alert("Temporary Ban for [banned_key]?",,"Yes","No")) + if("Yes") + temp = 1 + var/mins = 0 + if(minutes > GLOB.CMinutes) + mins = minutes - GLOB.CMinutes + mins = input(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440) as num|null + if(mins <= 0) + to_chat(usr, "[mins] is not a valid duration.") + return + minutes = GLOB.CMinutes + mins + duration = GetExp(minutes) + reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null + if(!reason) + return + if("No") + temp = 0 + duration = "Perma" + reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null + if(!reason) + return + + log_admin_private("[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]") + GLOB.Banlist.cd = "/base/[banfolder]" + WRITE_FILE(GLOB.Banlist["reason"], reason) + WRITE_FILE(GLOB.Banlist["temp"], temp) + WRITE_FILE(GLOB.Banlist["minutes"], minutes) + WRITE_FILE(GLOB.Banlist["bannedby"], usr.ckey) + GLOB.Banlist.cd = "/base" + unbanpanel() + + /////////////////////////////////////new ban stuff + + else if(href_list["appearanceban"]) + if(!check_rights(R_BAN)) + return + var/mob/M = locate(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 + + + if(jobban_isbanned(M, "appearance")) + switch(alert("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_private("[key_name(usr)] removed [key_name(M)]'s appearance ban.") + DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, "appearance") + if(M.client) + jobban_buildcache(M.client) + message_admins("[key_name_admin(usr)] removed [key_name_admin(M)]'s appearance ban.") + to_chat(M, "[usr.client.key] has removed your appearance ban.") + + else switch(alert("Appearance ban [M.key]?",,"Yes","No", "Cancel")) + if("Yes") + var/reason = input(usr,"Please State Reason.","Reason") as message|null + if(!reason) + return + var/severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") + if(!severity) + return + if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, "appearance")) + to_chat(usr, "Failed to apply ban.") + return + if(M.client) + jobban_buildcache(M.client) + ban_unban_log_save("[key_name(usr)] appearance banned [key_name(M)]. reason: [reason]") + log_admin_private("[key_name(usr)] appearance banned [key_name(M)]. \nReason: [reason]") + create_message("note", M.key, null, "Appearance banned - [reason]", null, null, 0, 0, null, 0, severity) + message_admins("[key_name_admin(usr)] appearance banned [key_name_admin(M)].") + to_chat(M, "You have been appearance banned by [usr.client.key].") + to_chat(M, "The reason is: [reason]") + to_chat(M, "Appearance ban can be lifted only upon request.") + var/bran = CONFIG_GET(string/banappeals) + if(bran) + to_chat(M, "To try to resolve this matter head to [bran]") + 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 = locate(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 + + var/dat = "Job-Ban Panel: [key_name(M)]" + + /***********************************WARNING!************************************ + The jobban stuff looks mangled and disgusting + But it looks beautiful in-game + -Nodrak + ************************************WARNING!***********************************/ + var/counter = 0 +//Regular jobs + //Command (Blue) + dat += "" + dat += "" + for(var/jobPos in GLOB.command_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 6) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
    Command Positions
    [jobPos][jobPos]
    " + + //Security (Red) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.security_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
    Security Positions
    [jobPos][jobPos]
    " + + //Engineering (Yellow) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.engineering_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
    Engineering Positions
    [jobPos][jobPos]
    " + + //Medical (White) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.medical_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
    Medical Positions
    [jobPos][jobPos]
    " + + //Science (Purple) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.science_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
    Science Positions
    [jobPos][jobPos]
    " + + //Supply (Brown) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.supply_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get COPYPASTE! + dat += "" + counter = 0 + dat += "
    Supply Positions
    [jobPos][jobPos]
    " + + //Civilian (Grey) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.civilian_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
    Civilian Positions
    [jobPos][jobPos]
    " + + //Non-Human (Green) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.nonhuman_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + + dat += "
    Non-human Positions
    [jobPos][jobPos]
    " + + //Ghost Roles (light light gray) + dat += "" + dat += "" + + //pAI + if(jobban_isbanned(M, ROLE_PAI)) + dat += "" + else + dat += "" + + + //Drones + if(jobban_isbanned(M, ROLE_DRONE)) + dat += "" + else + dat += "" + + + //Positronic Brains + if(jobban_isbanned(M, ROLE_POSIBRAIN)) + dat += "" + else + dat += "" + + //Sentience Potion Spawn + if(jobban_isbanned(M, ROLE_SENTIENCE)) + dat += "" + else + dat += "" + + //Deathsquad + if(jobban_isbanned(M, ROLE_DEATHSQUAD)) + dat += "" + else + dat += "" + + //Lavaland roles + if(jobban_isbanned(M, ROLE_LAVALAND)) + dat += "" + else + dat += "" + + dat += "
    Ghost Roles
    pAIpAIDroneDronePosibrainPosibrainSentience Potion SpawnSentience Potion SpawnDeathsquadDeathsquadLavalandLavaland
    " + + //Antagonist (Orange) + var/isbanned_dept = jobban_isbanned(M, ROLE_SYNDICATE) + dat += "" + dat += "" + + //Traitor + if(jobban_isbanned(M, ROLE_TRAITOR) || isbanned_dept) + dat += "" + else + dat += "" + + //Changeling + if(jobban_isbanned(M, ROLE_CHANGELING) || isbanned_dept) + dat += "" + else + dat += "" + + //Nuke Operative + if(jobban_isbanned(M, ROLE_OPERATIVE) || isbanned_dept) + dat += "" + else + dat += "" + + //Revolutionary + if(jobban_isbanned(M, ROLE_REV) || isbanned_dept) + dat += "" + else + dat += "" + + //Cultist + if(jobban_isbanned(M, ROLE_CULTIST) || isbanned_dept) + dat += "" + else + dat += "" + + dat += "" //So things dont get squished. + + //Servant of Ratvar + if(jobban_isbanned(M, ROLE_SERVANT_OF_RATVAR) || isbanned_dept) + dat += "" + else + dat += "" + + //Wizard + if(jobban_isbanned(M, ROLE_WIZARD) || isbanned_dept) + dat += "" + else + dat += "" + + //Abductor + if(jobban_isbanned(M, ROLE_ABDUCTOR) || isbanned_dept) + dat += "" + else + dat += "" + + //Alien + if(jobban_isbanned(M, ROLE_ALIEN) || isbanned_dept) + dat += "" + else + dat += "" + + //Gang + if(jobban_isbanned(M, ROLE_GANG) || isbanned_dept) + dat += "" + else + dat += "" + + + //Other Roles (black) + dat += "
    Antagonist Positions | " + dat += "Team Antagonists | " + dat += "Conversion Antagonists
    TraitorTraitorChangelingChangelingNuke OperativeNuke OperativeRevolutionaryRevolutionaryCultistCultist
    ServantServantWizardWizardAbductorAbductorAlienAlienGangGang
    " + dat += "" + + //Mind Transfer Potion + if(jobban_isbanned(M, ROLE_MIND_TRANSFER)) + dat += "" + else + dat += "" + + dat += "
    Other Roles
    Mind Transfer PotionMind Transfer Potion
    " + usr << browse(dat, "window=jobban2;size=800x450") + return + + //JOBBAN'S INNARDS + else if(href_list["jobban3"]) + if(!check_rights(R_BAN)) + return + var/mob/M = locate(href_list["jobban4"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(!SSjob) + to_chat(usr, "Jobs subsystem not initialized yet!") + return + //get jobs for department if specified, otherwise just return the one job in a list. + var/list/joblist = list() + switch(href_list["jobban3"]) + if("commanddept") + for(var/jobPos in GLOB.command_positions) + if(!jobPos) + continue + joblist += jobPos + if("securitydept") + for(var/jobPos in GLOB.security_positions) + if(!jobPos) + continue + joblist += jobPos + if("engineeringdept") + for(var/jobPos in GLOB.engineering_positions) + if(!jobPos) + continue + joblist += jobPos + if("medicaldept") + for(var/jobPos in GLOB.medical_positions) + if(!jobPos) + continue + joblist += jobPos + if("sciencedept") + for(var/jobPos in GLOB.science_positions) + if(!jobPos) + continue + joblist += jobPos + if("supplydept") + for(var/jobPos in GLOB.supply_positions) + if(!jobPos) + continue + joblist += jobPos + if("civiliandept") + for(var/jobPos in GLOB.civilian_positions) + if(!jobPos) + continue + joblist += jobPos + if("nonhumandept") + for(var/jobPos in GLOB.nonhuman_positions) + if(!jobPos) + continue + joblist += jobPos + if("ghostroles") + joblist += list(ROLE_PAI, ROLE_POSIBRAIN, ROLE_DRONE , ROLE_DEATHSQUAD, ROLE_LAVALAND, ROLE_SENTIENCE) + if("teamantags") + joblist += list(ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_GANG) + if("convertantags") + joblist += list(ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ALIEN) + if("otherroles") + joblist += list(ROLE_MIND_TRANSFER) + 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. + var/severity = null + switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) + if("Yes") + var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null + if(mins <= 0) + to_chat(usr, "[mins] is not a valid duration.") + return + var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null + if(!reason) + return + severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") + if(!severity) + return + var/msg + for(var/job in notbannedlist) + if(!DB_ban_record(BANTYPE_JOB_TEMP, M, mins, reason, job)) + to_chat(usr, "Failed to apply ban.") + return + if(M.client) + jobban_buildcache(M.client) + ban_unban_log_save("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes. reason: [reason]") + log_admin_private("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes.") + if(!msg) + msg = job + else + msg += ", [job]" + create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) + message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg] for [mins] minutes.") + to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] 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 Reason For Banning [M.key].","Reason") as message|null + severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") + if(!severity) + return + if(reason) + var/msg + for(var/job in notbannedlist) + if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, job)) + to_chat(usr, "Failed to apply ban.") + return + if(M.client) + jobban_buildcache(M.client) + ban_unban_log_save("[key_name(usr)] perma-jobbanned [key_name(M)] from [job]. reason: [reason]") + log_admin_private("[key_name(usr)] perma-banned [key_name(M)] from [job]") + if(!msg) + msg = job + else + msg += ", [job]" + create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) + message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg].") + to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] 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. + 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_private("[key_name(usr)] unbanned [key_name(M)] from [job]") + DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, job) + if(M.client) + jobban_buildcache(M.client) + if(!msg) + msg = job + else + msg += ", [job]" + else + continue + if(msg) + message_admins("[key_name_admin(usr)] unbanned [key_name_admin(M)] from [msg].") + to_chat(M, "You have been un-jobbanned by [usr.client.key] 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"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["boot2"]) + if(ismob(M)) + if(!check_if_greater_rights_than(M.client)) + to_chat(usr, "Error: They have more rights than you do.") + return + if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") + return + if(!M) + to_chat(usr, "Error: [M] no longer exists!") + return + if(!M.client) + to_chat(usr, "Error: [M] no longer has a client!") + return + to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].") + log_admin("[key_name(usr)] kicked [key_name(M)].") + message_admins("[key_name_admin(usr)] kicked [key_name_admin(M)].") + qdel(M.client) + + else if(href_list["addmessage"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addmessage"] + create_message("message", target_key, secret = 0) + + else if(href_list["addnote"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addnote"] + create_message("note", target_key) + + else if(href_list["addwatch"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addwatch"] + create_message("watchlist entry", target_key, secret = 1) + + else if(href_list["addmemo"]) + if(!check_rights(R_ADMIN)) + return + create_message("memo", secret = 0, browse = 1) + + else if(href_list["addmessageempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("message", secret = 0) + + else if(href_list["addnoteempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("note") + + else if(href_list["addwatchempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("watchlist entry", secret = 1) + + else if(href_list["deletemessage"]) + if(!check_rights(R_ADMIN)) + return + var/safety = alert("Delete message/note?",,"Yes","No"); + if (safety == "Yes") + var/message_id = href_list["deletemessage"] + delete_message(message_id) + + else if(href_list["deletemessageempty"]) + if(!check_rights(R_ADMIN)) + return + var/safety = alert("Delete message/note?",,"Yes","No"); + if (safety == "Yes") + var/message_id = href_list["deletemessageempty"] + delete_message(message_id, browse = TRUE) + + else if(href_list["editmessage"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessage"] + edit_message(message_id) + + else if(href_list["editmessageempty"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageempty"] + edit_message(message_id, browse = 1) + + else if(href_list["editmessageexpiry"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageexpiry"] + edit_message_expiry(message_id) + + else if(href_list["editmessageexpiryempty"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageexpiryempty"] + edit_message_expiry(message_id, browse = 1) + + else if(href_list["editmessageseverity"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageseverity"] + edit_message_severity(message_id) + + else if(href_list["secretmessage"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["secretmessage"] + toggle_message_secrecy(message_id) + + else if(href_list["searchmessages"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["searchmessages"] + browse_messages(index = target) + + else if(href_list["nonalpha"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["nonalpha"] + target = text2num(target) + browse_messages(index = target) + + else if(href_list["showmessages"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["showmessages"] + browse_messages(index = target) + + else if(href_list["showmemo"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("memo") + + else if(href_list["showwatch"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("watchlist entry") + + else if(href_list["showwatchfilter"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("watchlist entry", filter = 1) + + else if(href_list["showmessageckey"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["showmessageckey"] + var/agegate = TRUE + if (href_list["showall"]) + agegate = FALSE + browse_messages(target_ckey = target, agegate = agegate) + + else if(href_list["showmessageckeylinkless"]) + var/target = href_list["showmessageckeylinkless"] + browse_messages(target_ckey = target, linkless = 1) + + else if(href_list["messageedits"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = sanitizeSQL("[href_list["messageedits"]]") + var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") + if(!query_get_message_edits.warn_execute()) + qdel(query_get_message_edits) + return + if(query_get_message_edits.NextRow()) + var/edit_log = query_get_message_edits.item[1] + if(!QDELETED(usr)) + var/datum/browser/browser = new(usr, "Note edits", "Note edits") + browser.set_content(jointext(edit_log, "")) + browser.open() + qdel(query_get_message_edits) + + else if(href_list["newban"]) + if(!check_rights(R_BAN)) + return + + var/mob/M = locate(href_list["newban"]) + if(!ismob(M)) + return + + if(M.client && M.client.holder) + return //admins cannot be banned. Even if they could, the ban doesn't affect them anyway + + switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) + if("Yes") + var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null + if(mins <= 0) + to_chat(usr, "[mins] is not a valid duration.") + return + var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null + if(!reason) + return + if(!DB_ban_record(BANTYPE_TEMP, M, mins, reason)) + to_chat(usr, "Failed to apply ban.") + return + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins) + ban_unban_log_save("[key_name(usr)] has banned [key_name(M)]. - Reason: [reason] - This will be removed in [mins] minutes.") + to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") + to_chat(M, "This is a temporary ban, it will be removed in [mins] minutes. The round ID is [GLOB.round_id].") + var/bran = CONFIG_GET(string/banappeals) + if(bran) + to_chat(M, "To try to resolve this matter head to [bran]") + else + to_chat(M, "No ban appeals URL has been set.") + log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [key_name(M)]\nThis will be removed in [mins] minutes.") + var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis will be removed in [mins] minutes." + message_admins(msg) + var/datum/admin_help/AH = M.client ? M.client.current_ticket : null + if(AH) + AH.Resolve() + qdel(M.client) + if("No") + var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null + if(!reason) + return + switch(alert(usr,"IP ban?",,"Yes","No","Cancel")) + if("Cancel") + return + if("Yes") + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) + if("No") + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0) + to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") + to_chat(M, "This is a permanent ban. The round ID is [GLOB.round_id].") + var/bran = CONFIG_GET(string/banappeals) + if(bran) + to_chat(M, "To try to resolve this matter head to [bran]") + else + to_chat(M, "No ban appeals URL has been set.") + if(!DB_ban_record(BANTYPE_PERMA, M, -1, reason)) + to_chat(usr, "Failed to apply ban.") + return + ban_unban_log_save("[key_name(usr)] has permabanned [key_name(M)]. - Reason: [reason] - This is a permanent ban.") + log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [reason]\nThis is a permanent ban.") + var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis is a permanent ban." + message_admins(msg) + var/datum/admin_help/AH = M.client ? M.client.current_ticket : null + if(AH) + AH.Resolve() + qdel(M.client) + if("Cancel") + return + + else if(href_list["mute"]) + if(!check_rights(R_ADMIN)) + return + cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) + + else if(href_list["c_mode"]) + return HandleCMode() + + else if(href_list["f_secret"]) + return HandleFSecret() + +//Dynamic mode + else if(href_list["f_dynamic_roundstart"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) + var/roundstart_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) + var/datum/dynamic_ruleset/roundstart/newrule = new rule() + roundstart_rules[newrule.name] = newrule + var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in roundstart_rules + if (added_rule) + GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] + log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") + message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) + Game() + + else if(href_list["f_dynamic_roundstart_clear"]) + if(!check_rights(R_ADMIN)) + return + GLOB.dynamic_forced_roundstart_ruleset = list() + Game() + log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") + message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) + + else if(href_list["f_dynamic_roundstart_remove"]) + if(!check_rights(R_ADMIN)) + return + var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) + GLOB.dynamic_forced_roundstart_ruleset -= rule + Game() + log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") + message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) + + else if(href_list["f_dynamic_latejoin"]) + if(!check_rights(R_ADMIN)) + return + if(!SSticker || !SSticker.mode) + return alert(usr, "The game must start first.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/latejoin_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin)) + var/datum/dynamic_ruleset/latejoin/newrule = new rule() + latejoin_rules[newrule.name] = newrule + var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in latejoin_rules + if (added_rule) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.forced_latejoin_rule = latejoin_rules[added_rule] + log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") + message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1) + Game() + + else if(href_list["f_dynamic_latejoin_clear"]) + if(!check_rights(R_ADMIN)) + return + if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.forced_latejoin_rule = null + Game() + log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") + message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1) + + else if(href_list["f_dynamic_midround"]) + if(!check_rights(R_ADMIN)) + return + if(!SSticker || !SSticker.mode) + return alert(usr, "The game must start first.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/midround_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/midround)) + var/datum/dynamic_ruleset/midround/newrule = new rule() + midround_rules[newrule.name] = rule + var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in midround_rules + if (added_rule) + var/datum/game_mode/dynamic/mode = SSticker.mode + log_admin("[key_name(usr)] executed the [added_rule] ruleset.") + message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1) + mode.picking_specific_rule(midround_rules[added_rule],1) + + else if (href_list["f_dynamic_options"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_centre"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num + if (new_centre < -5 || new_centre > 5) + return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) + + log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") + message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) + GLOB.dynamic_curve_centre = new_centre + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_width"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num + if (new_width < 0.5 || new_width > 4) + return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) + + log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") + message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) + GLOB.dynamic_curve_width = new_width + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_latejoin_min"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num + if(new_min <= 0) + return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) + if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) + return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") + message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) + GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_latejoin_max"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num + if(new_max <= 0) + return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) + if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) + return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") + message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) + GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_midround_min"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num + if(new_min <= 0) + return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) + if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) + return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") + message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) + GLOB.dynamic_midround_delay_min = (new_min MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_midround_max"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num + if(new_max <= 0) + return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) + if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) + return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") + message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) + GLOB.dynamic_midround_delay_max = (new_max MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_force_extended"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended + log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") + message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_no_stacking"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking + log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") + message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_classic_secret"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret + log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") + message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_stacking_limit"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num + log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") + message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_high_pop_limit"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num + if (new_value < 0) + return alert(usr, "Only positive values allowed!", null, null, null, null) + GLOB.dynamic_high_pop_limit = new_value + + log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") + message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_forced_threat"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num + if (new_value > 100) + return alert(usr, "The value must be be under 100.", null, null, null, null) + GLOB.dynamic_forced_threat_level = new_value + + log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") + message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_chaos_level"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_value = input(usr, "Enter the chaos level for dynamic mode.", "Chaos level") as num + if (new_value > 5 || new_value < 0) + return alert(usr, "The value must be between 0 and 5.", null, null, null, null) + GLOB.dynamic_chaos_level = new_value + + log_admin("[key_name(usr)] set 'dynamic_chaos_level' to [GLOB.dynamic_chaos_level].") + message_admins("[key_name(usr)] set 'dynamic_chaos_level' to [GLOB.dynamic_chaos_level].") + dynamic_mode_options(usr) +//End Dynamic mode + + else if(href_list["c_mode2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if (SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + GLOB.master_mode = href_list["c_mode2"] + log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") + message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") + to_chat(world, "The mode is now: [GLOB.master_mode]") + Game() // updates the main game menu + SSticker.save_mode(GLOB.master_mode) + HandleCMode() + + else if(href_list["f_secret2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + GLOB.secret_force_mode = href_list["f_secret2"] + log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + Game() // updates the main game menu + HandleFSecret() + + else if(href_list["monkeyone"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + 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)].") + H.monkeyize() + + else if(href_list["humanone"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/monkey/Mo = locate(href_list["humanone"]) + if(!istype(Mo)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/monkey.") + return + + log_admin("[key_name(usr)] attempting to humanize [key_name(Mo)].") + message_admins("[key_name_admin(usr)] attempting to humanize [key_name_admin(Mo)].") + Mo.humanize() + + else if(href_list["corgione"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["corgione"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + 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)].") + H.corgize() + + + else if(href_list["forcespeech"]) + if(!check_rights(R_FUN)) + return + + var/mob/M = locate(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, forced = "admin 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["makeeligible"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["makeeligible"]) + if(!ismob(M)) + to_chat(usr, "this can only be used on instances of type /mob.") + if(M.ckey in GLOB.client_ghost_timeouts) + GLOB.client_ghost_timeouts -= M.ckey + + else if(href_list["sendtoprison"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendtoprison"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + + if(alert(usr, "Send [key_name(M)] to Prison?", "Message", "Yes", "No") != "Yes") + return + + M.forceMove(pick(GLOB.prisonwarp)) + to_chat(M, "You have been sent to Prison!") + + log_admin("[key_name(usr)] has sent [key_name(M)] to Prison!") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to Prison!") + + else if(href_list["sendbacktolobby"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(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(usr)] has sent [key_name(M)] back to the Lobby.") + + var/mob/dead/new_player/NP = new() + NP.ckey = M.ckey + qdel(M) + + else if(href_list["tdome1"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdome1"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdome1)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 1)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 1)") + + else if(href_list["tdome2"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdome2"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdome2)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 2)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 2)") + + else if(href_list["tdomeadmin"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdomeadmin"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + var/mob/living/L = M + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeadmin)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Admin.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Admin.)") + + else if(href_list["tdomeobserve"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdomeobserve"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + if(ishuman(L)) + var/mob/living/carbon/human/observer = L + 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/sneakers/black(observer), SLOT_SHOES) + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeobserve)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Observer.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Observer.)") + + else if(href_list["revive"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/living/L = locate(href_list["revive"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /mob/living.") + return + + L.revive(full_heal = 1, admin_revive = 1) + message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!") + 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 = locate(href_list["makeai"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + + message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") + log_admin("[key_name(usr)] AIized [key_name(H)].") + H.AIize() + + else if(href_list["makealien"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makealien"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + + usr.client.cmd_admin_alienize(H) + + else if(href_list["makeslime"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeslime"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + + usr.client.cmd_admin_slimeize(H) + + else if(href_list["makeblob"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeblob"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + + usr.client.cmd_admin_blobize(H) + + + else if(href_list["makerobot"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makerobot"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + + usr.client.cmd_admin_robotize(H) + + else if(href_list["makeanimal"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/M = locate(href_list["makeanimal"]) + if(isnewplayer(M)) + to_chat(usr, "This cannot be used on instances of type /mob/dead/new_player.") + return + + usr.client.cmd_admin_animalize(M) + + else if(href_list["adminplayeropts"]) + var/mob/M = locate(href_list["adminplayeropts"]) + show_player_panel(M) + + else if(href_list["adminplayerobservefollow"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/atom/movable/AM = locate(href_list["adminplayerobservefollow"]) + + var/client/C = usr.client + if(!isobserver(usr)) + C.admin_ghost() + var/mob/dead/observer/A = C.mob + A.ManualFollow(AM) + + else if(href_list["admingetmovable"]) + if(!check_rights(R_ADMIN)) + return + + var/atom/movable/AM = locate(href_list["admingetmovable"]) + if(QDELETED(AM)) + return + AM.forceMove(get_turf(usr)) + + else if(href_list["adminplayerobservecoodjump"]) + if(!isobserver(usr) && !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"]) + if(!check_rights(R_ADMIN)) + return + output_ai_laws() + + else if(href_list["admincheckdevilinfo"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["admincheckdevilinfo"]) + output_devil_info(M) + + else if(href_list["adminmoreinfo"]) + var/mob/M = locate(href_list["adminmoreinfo"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + + var/location_description = "" + var/special_role_description = "" + var/health_description = "" + var/gender_description = "" + var/turf/T = get_turf(M) + + //Location + if(isturf(T)) + if(isarea(T.loc)) + location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z] in area [T.loc])" + else + location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z])" + + //Job + antagonist + if(M.mind) + special_role_description = "Role: [M.mind.assigned_role]; Antagonist: [M.mind.special_role]" + else + special_role_description = "Role: Mind datum missing Antagonist: Mind datum missing" + + //Health + if(isliving(M)) + var/mob/living/L = M + var/status + switch (M.stat) + if(CONSCIOUS) + status = "Alive" + if(SOFT_CRIT) + status = "Dying" + if(UNCONSCIOUS) + status = "[L.InCritical() ? "Unconscious and Dying" : "Unconscious"]" + if(DEAD) + status = "Dead" + health_description = "Status = [status]" + health_description += "
    Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getOrganLoss(ORGAN_SLOT_BRAIN)] - Stamina: [L.getStaminaLoss()]" + else + health_description = "This mob type has no health to speak of." + + //Gender + switch(M.gender) + if(MALE,FEMALE) + gender_description = "[M.gender]" + else + gender_description = "[M.gender]" + + to_chat(src.owner, "Info about [M.name]: ") + to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]") + to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];") + to_chat(src.owner, "Location = [location_description];") + to_chat(src.owner, "[special_role_description]") + to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M)) + + else if(href_list["addjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Add = href_list["addjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Add) + job.total_positions += 1 + break + + src.manage_free_slots() + + + else if(href_list["customjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Add = href_list["customjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Add) + var/newtime = null + newtime = input(usr, "How many jebs do you want?", "Add wanted posters", "[newtime]") as num|null + if(!newtime) + to_chat(src.owner, "Setting to amount of positions filled for the job") + job.total_positions = job.current_positions + break + job.total_positions = newtime + + src.manage_free_slots() + + else if(href_list["removejobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Remove = href_list["removejobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Remove && job.total_positions - job.current_positions > 0) + job.total_positions -= 1 + break + + src.manage_free_slots() + + else if(href_list["unlimitjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Unlimit = href_list["unlimitjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Unlimit) + job.total_positions = -1 + break + + src.manage_free_slots() + + else if(href_list["limitjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Limit = href_list["limitjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Limit) + job.total_positions = job.current_positions + break + + src.manage_free_slots() + + + else if(href_list["adminspawncookie"]) + if(!check_rights(R_ADMIN|R_FUN)) + return + + var/mob/living/carbon/human/H = locate(href_list["adminspawncookie"]) + if(!ishuman(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + + var/obj/item/reagent_containers/food/snacks/cookie/cookie = new(H) + if(H.put_in_hands(cookie)) + H.update_inv_hands() + else + qdel(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(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") + return + + log_admin("[key_name(H)] got their cookie, spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] got their cookie, spawned by [key_name(src.owner)].") + SSblackbox.record_feedback("amount", "admin_cookies_spawned", 1) + to_chat(H, "Your prayers have been answered!! You received the best cookie!") + SEND_SOUND(H, sound('sound/effects/pray_chaplain.ogg')) + + else if(href_list["adminsmite"]) + if(!check_rights(R_ADMIN|R_FUN)) + return + + var/mob/living/carbon/human/H = locate(href_list["adminsmite"]) in GLOB.mob_list + if(!H || !istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + + usr.client.smite(H) + + else if(href_list["CentComReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["CentComReply"]) + usr.client.admin_headset_message(M, RADIO_CHANNEL_CENTCOM) + + else if(href_list["SyndicateReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["SyndicateReply"]) + usr.client.admin_headset_message(M, RADIO_CHANNEL_SYNDICATE) + + else if(href_list["HeadsetMessage"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["HeadsetMessage"]) + usr.client.admin_headset_message(M) + + else if(href_list["reject_custom_name"]) + if(!check_rights(R_ADMIN)) + return + var/obj/item/station_charter/charter = locate(href_list["reject_custom_name"]) + if(istype(charter)) + charter.reject_proposed(usr) + else if(href_list["jumpto"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/mob/M = locate(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 = locate(href_list["getmob"]) + usr.client.Getmob(M) + + else if(href_list["sendmob"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendmob"]) + usr.client.sendmob(M) + + else if(href_list["narrateto"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["narrateto"]) + usr.client.cmd_admin_direct_narrate(M) + + else if(href_list["subtlemessage"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["subtlemessage"]) + usr.client.cmd_admin_subtle_message(M) + + else if(href_list["individuallog"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["individuallog"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + + show_individual_logging_panel(M, href_list["log_src"], href_list["log_type"]) + else if(href_list["languagemenu"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["languagemenu"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + var/datum/language_holder/H = M.get_language_holder() + H.open_language_menu(usr) + + else if(href_list["traitor"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + + var/mob/M = locate(href_list["traitor"]) + if(!ismob(M)) + var/datum/mind/D = M + if(!istype(D)) + to_chat(usr, "This can only be used on instances of type /mob and /mind") + return + else + D.traitor_panel() + else + show_traitor_panel(M) + + else if(href_list["borgpanel"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["borgpanel"]) + if(!iscyborg(M)) + to_chat(usr, "This can only be used on cyborgs") + else + open_borgopanel(M) + + else if(href_list["initmind"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["initmind"]) + if(!ismob(M) || M.mind) + to_chat(usr, "This can only be used on instances on mindless mobs") + return + M.mind_initialize() + + 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["dupe_marked_datum"]) + if(!check_rights(R_SPAWN)) + return + return DuplicateObject(marked_datum, perfectcopy=1, newloc=get_turf(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 = CLAMP(text2num(href_list["object_count"]), 1, 100) + 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/obj_dir = text2num(href_list["object_dir"]) + if(obj_dir && !(obj_dir in list(1,2,4,8,5,6,9,10))) + obj_dir = null + 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) && !iscyborg(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(!QDELETED(O)) + O.flags_1 |= ADMIN_SPAWNED_1 + if(obj_dir) + O.setDir(obj_dir) + if(obj_name) + O.name = obj_name + if(ismob(O)) + var/mob/M = O + M.real_name = obj_name + if(where == "inhand" && isliving(usr) && isitem(O)) + var/mob/living/L = usr + var/obj/item/I = O + L.put_in_hands(I) + if(iscyborg(L)) + var/mob/living/silicon/robot/R = L + if(R.module) + R.module.add_module(I, TRUE, TRUE) + R.activate_module(I) + + + 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["secrets"]) + Secrets_topic(href_list["secrets"],href_list) + + else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here + if(!check_rights(R_ADMIN)) + return + 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"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_channel.channel_name = stripped_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"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked + src.access_news_network() + + else if(href_list["ac_submit_new_channel"]) + if(!check_rights(R_ADMIN)) + return + var/check = 0 + for(var/datum/newscaster/feed_channel/FC in GLOB.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") + GLOB.news_network.CreateFeedChannel(src.admincaster_feed_channel.channel_name, src.admin_signature, src.admincaster_feed_channel.locked, 1) + SSblackbox.record_feedback("tally", "newscaster_channels", 1, src.admincaster_feed_channel.channel_name) + log_admin("[key_name(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"]) + if(!check_rights(R_ADMIN)) + return + var/list/available_channels = list() + for(var/datum/newscaster/feed_channel/F in GLOB.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"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_message.body = adminscrub(input(usr, "Write your Feed story.", "Network Channel Handler", "")) + while (findtext(src.admincaster_feed_message.returnBody(-1)," ") == 1) + src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.returnBody(-1),2,length(src.admincaster_feed_message.returnBody(-1))+1) + src.access_news_network() + + else if(href_list["ac_submit_new_message"]) + if(!check_rights(R_ADMIN)) + return + if(src.admincaster_feed_message.returnBody(-1) =="" || src.admincaster_feed_message.returnBody(-1) =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) + src.admincaster_screen = 6 + else + GLOB.news_network.SubmitArticle(src.admincaster_feed_message.returnBody(-1), src.admin_signature, src.admincaster_feed_channel.channel_name, null, 1) + SSblackbox.record_feedback("amount", "newscaster_stories", 1) + src.admincaster_screen=4 + + for(var/obj/machinery/newscaster/NEWSCASTER in GLOB.allCasters) + NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) + + log_admin("[key_name(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") + src.access_news_network() + + else if(href_list["ac_create_channel"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=2 + src.access_news_network() + + else if(href_list["ac_create_feed_story"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=3 + src.access_news_network() + + else if(href_list["ac_menu_censor_story"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=10 + src.access_news_network() + + else if(href_list["ac_menu_censor_channel"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=11 + src.access_news_network() + + else if(href_list["ac_menu_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/already_wanted = 0 + if(GLOB.news_network.wanted_issue.active) + already_wanted = 1 + + if(already_wanted) + src.admincaster_wanted_message.criminal = GLOB.news_network.wanted_issue.criminal + src.admincaster_wanted_message.body = GLOB.news_network.wanted_issue.body + src.admincaster_screen = 14 + src.access_news_network() + + else if(href_list["ac_set_wanted_name"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_wanted_message.criminal = adminscrub(input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) + while(findtext(src.admincaster_wanted_message.criminal," ") == 1) + src.admincaster_wanted_message.criminal = copytext(admincaster_wanted_message.criminal,2,length(admincaster_wanted_message.criminal)+1) + src.access_news_network() + + else if(href_list["ac_set_wanted_desc"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_wanted_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_wanted_message.body," ") == 1) + src.admincaster_wanted_message.body = copytext(src.admincaster_wanted_message.body,2,length(src.admincaster_wanted_message.body)+1) + src.access_news_network() + + else if(href_list["ac_submit_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/input_param = text2num(href_list["ac_submit_wanted"]) + if(src.admincaster_wanted_message.criminal == "" || src.admincaster_wanted_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 + GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature, null, 1, 1) + src.admincaster_screen = 15 + else + GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature) + src.admincaster_screen = 19 + log_admin("[key_name(usr)] issued a Station-wide Wanted Notification for [src.admincaster_wanted_message.criminal]!") + src.access_news_network() + + else if(href_list["ac_cancel_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/choice = alert("Please confirm Wanted Issue removal.","Network Security Handler","Confirm","Cancel") + if(choice=="Confirm") + GLOB.news_network.deleteWanted() + src.admincaster_screen=17 + src.access_news_network() + + else if(href_list["ac_censor_channel_author"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) + FC.toggleCensorAuthor() + src.access_news_network() + + else if(href_list["ac_censor_channel_story_author"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) + MSG.toggleCensorAuthor() + src.access_news_network() + + else if(href_list["ac_censor_channel_story_body"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) + MSG.toggleCensorBody() + src.access_news_network() + + else if(href_list["ac_pick_d_notice"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/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"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) + FC.toggleCensorDclass() + src.access_news_network() + + else if(href_list["ac_view"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=1 + src.access_news_network() + + else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen = text2num(href_list["ac_setScreen"]) + if (src.admincaster_screen == 0) + if(src.admincaster_feed_channel) + src.admincaster_feed_channel = new /datum/newscaster/feed_channel + if(src.admincaster_feed_message) + src.admincaster_feed_message = new /datum/newscaster/feed_message + if(admincaster_wanted_message) + admincaster_wanted_message = new /datum/newscaster/wanted_message + src.access_news_network() + + else if(href_list["ac_show_channel"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/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"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/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"]) + if(!check_rights(R_ADMIN)) + return + src.access_news_network() + + else if(href_list["ac_set_signature"]) + if(!check_rights(R_ADMIN)) + return + src.admin_signature = adminscrub(input(usr, "Provide your desired signature.", "Network Identity Handler", "")) + src.access_news_network() + + else if(href_list["ac_del_comment"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_comment/FC = locate(href_list["ac_del_comment"]) + var/datum/newscaster/feed_message/FM = locate(href_list["ac_del_comment_msg"]) + FM.comments -= FC + qdel(FC) + src.access_news_network() + + else if(href_list["ac_lock_comment"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/FM = locate(href_list["ac_lock_comment"]) + FM.locked ^= 1 + src.access_news_network() + + else if(href_list["check_antagonist"]) + if(!check_rights(R_ADMIN)) + return + usr.client.check_antagonists() + + else if(href_list["kick_all_from_lobby"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker.IsRoundInProgress()) + var/afkonly = text2num(href_list["afkonly"]) + if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby??","Message","Yes","Cancel") != "Yes") + to_chat(usr, "Kick clients from lobby aborted") + return + var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", 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["create_outfit"]) + if(!check_rights(R_ADMIN)) + return + + var/datum/outfit/O = new /datum/outfit + //swap this for js dropdowns sometime + O.name = href_list["outfit_name"] + O.uniform = text2path(href_list["outfit_uniform"]) + O.shoes = text2path(href_list["outfit_shoes"]) + O.gloves = text2path(href_list["outfit_gloves"]) + O.suit = text2path(href_list["outfit_suit"]) + O.head = text2path(href_list["outfit_head"]) + O.back = text2path(href_list["outfit_back"]) + O.mask = text2path(href_list["outfit_mask"]) + O.glasses = text2path(href_list["outfit_glasses"]) + O.r_hand = text2path(href_list["outfit_r_hand"]) + O.l_hand = text2path(href_list["outfit_l_hand"]) + O.suit_store = text2path(href_list["outfit_s_store"]) + O.l_pocket = text2path(href_list["outfit_l_pocket"]) + O.r_pocket = text2path(href_list["outfit_r_pocket"]) + O.id = text2path(href_list["outfit_id"]) + O.belt = text2path(href_list["outfit_belt"]) + O.ears = text2path(href_list["outfit_ears"]) + + GLOB.custom_outfits.Add(O) + message_admins("[key_name(usr)] created \"[O.name]\" outfit!") + + else if(href_list["set_selfdestruct_code"]) + if(!check_rights(R_ADMIN)) + return + var/code = random_nukecode() + for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) + SD.r_code = code + message_admins("[key_name_admin(usr)] has set the self-destruct \ + code to \"[code]\".") + + else if(href_list["add_station_goal"]) + if(!check_rights(R_ADMIN)) + 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 = input("Enter goal name:") as text|null + if(!newname) + return + G.name = newname + var/description = input("Enter CentCom message contents:") as message|null + if(!description) + return + G.report_message = description + message_admins("[key_name(usr)] created \"[G.name]\" station goal.") + SSticker.mode.station_goals += G + modify_goals() + + else if(href_list["viewruntime"]) + var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) + if(!istype(error_viewer)) + to_chat(usr, "That runtime viewer no longer exists.") + return + + if(href_list["viewruntime_backto"]) + error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) + else + error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) + + else if(href_list["showrelatedacc"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["client"]) in GLOB.clients + var/thing_to_check + if(href_list["showrelatedacc"] == "cid") + thing_to_check = C.related_accounts_cid + else + thing_to_check = C.related_accounts_ip + thing_to_check = splittext(thing_to_check, ", ") + + + var/list/dat = list("Related accounts by [uppertext(href_list["showrelatedacc"])]:") + dat += thing_to_check + + usr << browse(dat.Join("
    "), "window=related_[C];size=420x300") + else if(href_list["centcomlookup"]) + if(!check_rights(R_ADMIN)) + return + + if(!CONFIG_GET(string/centcom_ban_db)) + to_chat(usr, "Centcom Galactic Ban DB is disabled!") + return + + var/ckey = href_list["centcomlookup"] + + // Make the request + var/datum/http_request/request = new() + request.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/centcom_ban_db)]/[ckey]", "", "") + request.begin_async() + UNTIL(request.is_complete() || !usr) + if (!usr) + return + var/datum/http_response/response = request.into_response() + + var/list/bans + + var/list/dat = list("") + + if(response.errored) + dat += "
    Failed to connect to CentCom." + else if(response.status_code != 200) + dat += "
    Failed to connect to CentCom. Status code: [response.status_code]" + else + if(response.body == "[]") + dat += "
    0 bans detected for [ckey]
    " + else + bans = json_decode(response["body"]) + dat += "
    [bans.len] ban\s detected for [ckey]
    " + for(var/list/ban in bans) + dat += "Server: [sanitize(ban["sourceName"])]
    " + dat += "RP Level: [sanitize(ban["sourceRoleplayLevel"])]
    " + dat += "Type: [sanitize(ban["type"])]
    " + dat += "Banned By: [sanitize(ban["bannedBy"])]
    " + dat += "Reason: [sanitize(ban["reason"])]
    " + dat += "Datetime: [sanitize(ban["bannedOn"])]
    " + var/expiration = ban["expires"] + dat += "Expires: [expiration ? "[sanitize(expiration)]" : "Permanent"]
    " + if(ban["type"] == "job") + dat += "Jobs: " + var/list/jobs = ban["jobs"] + dat += sanitize(jobs.Join(", ")) + dat += "
    " + dat += "
    " + + dat += "
    " + var/datum/browser/popup = new(usr, "centcomlookup-[ckey]", "
    Central Command Galactic Ban Database
    ", 700, 600) + popup.set_content(dat.Join()) + popup.open(0) + + + else if(href_list["modantagrep"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["mob"]) in GLOB.mob_list + var/client/C = M.client + usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) + show_player_panel(M) + + else if(href_list["slowquery"]) + if(!check_rights(R_ADMIN)) + return + var/answer = href_list["slowquery"] + if(answer == "yes") + log_query_debug("[usr.key] | Reported a server hang") + if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") + var/response = input(usr,"What were you just doing?","Query server hang report") as null|text + if(response) + log_query_debug("[usr.key] | [response]") + else if(answer == "no") + log_query_debug("[usr.key] | Reported no server hang") + +/datum/admins/proc/HandleCMode() + if(!check_rights(R_ADMIN)) + return + + if(SSticker.HasRoundStarted()) + 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: [GLOB.master_mode]"} + usr << browse(dat, "window=c_mode") + +/datum/admins/proc/HandleFSecret() + if(!check_rights(R_ADMIN)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.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: [GLOB.secret_force_mode]"} + usr << browse(dat, "window=f_secret") + +/datum/admins/proc/makeMentor(ckey) + if(!usr.client) + return + if (!check_rights(0)) + return + if(!ckey) + return + var/client/C = GLOB.directory[ckey] + if(C) + if(check_rights_for(C, R_ADMIN,0)) + to_chat(usr, "The client chosen is an admin! Cannot mentorize.") + return + if(SSdbcore.Connect()) + var/datum/DBQuery/query_get_mentor = SSdbcore.NewQuery("SELECT id FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") + if(query_get_mentor.NextRow()) + to_chat(usr, "[ckey] is already a mentor.") + return + var/datum/DBQuery/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") + if(!query_add_mentor.warn_execute()) + return + var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") + if(!query_add_admin_log.warn_execute()) + return + else + to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") + new /datum/mentors(ckey) + to_chat(usr, "New mentor added.") + +/datum/admins/proc/removeMentor(ckey) + if(!usr.client) + return + if (!check_rights(0)) + return + if(!ckey) + return + var/client/C = GLOB.directory[ckey] + if(C) + if(check_rights_for(C, R_ADMIN,0)) + to_chat(usr, "The client chosen is an admin, not a mentor! Cannot de-mentorize.") + return + C.remove_mentor_verbs() + C.mentor_datum = null + GLOB.mentors -= C + if(SSdbcore.Connect()) + var/datum/DBQuery/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") + if(!query_remove_mentor.warn_execute()) + return + var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed mentor [ckey]');") + if(!query_add_admin_log.warn_execute()) + return + else + to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") + to_chat(usr, "Mentor removed.") diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index 6ddc75c7..9daa417a 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -1,644 +1,644 @@ -GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "force_ending")) -GLOBAL_PROTECT(VVlocked) -GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays", "resize")) -GLOBAL_PROTECT(VVicon_edit_lock) -GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey")) -GLOBAL_PROTECT(VVckey_edit) -GLOBAL_LIST_INIT(VVpixelmovement, list("step_x", "step_y", "bound_height", "bound_width", "bound_x", "bound_y")) -GLOBAL_PROTECT(VVpixelmovement) - - -/client/proc/vv_get_class(var/var_name, var/var_value) - if(isnull(var_value)) - . = VV_NULL - - else if (isnum(var_value)) - if (var_name in GLOB.bitfields) - . = VV_BITFIELD - else - . = 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, /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 - . = VV_NULL - -/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes, var_name) - . = 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_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_BITFIELD) - .["value"] = input_bitfield(usr, "Editing bitfield: [var_name]", var_name, current_value) - 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_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 - var/atom/newguy = new type() - newguy.datum_flags |= DF_VAR_EDITED - .["value"] = newguy - - if (VV_NEW_DATUM) - var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) - if (!type) - .["class"] = null - return - .["type"] = type - var/datum/newguy = new type() - newguy.datum_flags |= DF_VAR_EDITED - .["value"] = newguy - - 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 - var/datum/newguy = new type() - if(istype(newguy)) - newguy.datum_flags |= DF_VAR_EDITED - .["value"] = newguy - - - 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) //hehe - - 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) == FALSE) - 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 its 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(objectvar, variable) - - to_chat(src, "Variable appears to be [uppertext(default)].") - - to_chat(src, "Variable contains: [variable]") - - if(default == VV_NUM) - var/dir_text = "" - var/tdir = variable - if(tdir > 0 && tdir < 16) - if(tdir & 1) - dir_text += "NORTH" - if(tdir & 2) - dir_text += "SOUTH" - if(tdir & 4) - dir_text += "EAST" - if(tdir & 8) - dir_text += "WEST" - - if(dir_text) - to_chat(usr, "If a direction, direction is: [dir_text]") - - var/original_var = variable - - 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) == FALSE) - 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 GLOB.VVlocked) - if(!check_rights(R_DEBUG)) - return FALSE - if(param_var_name in GLOB.VVckey_edit) - if(!check_rights(R_SPAWN|R_DEBUG)) - return FALSE - if(param_var_name in GLOB.VVicon_edit_lock) - if(!check_rights(R_FUN|R_DEBUG)) - return FALSE - if(param_var_name in GLOB.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 - - var_value = O.vars[variable] - if(!vv_varname_lockcheck(variable)) - return - if(istype(O, /datum/armor)) - var/prompt = alert(src, "Editing this var changes this value on potentially thousands of items that share the same combination of armor values. If you want to edit the armor of just one item, use the \"Modify armor values\" dropdown item", "DANGER", "ABORT ", "Continue", " ABORT") - if (prompt != "Continue") - return - - - var/default = vv_get_class(variable, 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(var_value > 0 && var_value < 16) - if(var_value & 1) - dir_text += "NORTH" - if(var_value & 2) - dir_text += "SOUTH" - if(var_value & 4) - dir_text += "EAST" - if(var_value & 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), var_name = variable) - 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) == FALSE) - to_chat(src, "Your edit was rejected by the object.") - return - vv_update_display(O, "varedited", VV_MSG_EDITED) - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VAR_EDIT, args) - log_world("### VarEdit by [key_name(src)]: [O.type] [variable]=[var_value] => [var_new]") - log_admin("[key_name(src)] modified [original_name]'s [variable] from [html_encode("[var_value]")] to [html_encode("[var_new]")]") - var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] from [var_value] to [var_new]" - message_admins(msg) - admin_ticket_log(O, msg) +GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "force_ending")) +GLOBAL_PROTECT(VVlocked) +GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays", "resize")) +GLOBAL_PROTECT(VVicon_edit_lock) +GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey")) +GLOBAL_PROTECT(VVckey_edit) +GLOBAL_LIST_INIT(VVpixelmovement, list("step_x", "step_y", "bound_height", "bound_width", "bound_x", "bound_y")) +GLOBAL_PROTECT(VVpixelmovement) + + +/client/proc/vv_get_class(var/var_name, var/var_value) + if(isnull(var_value)) + . = VV_NULL + + else if (isnum(var_value)) + if (var_name in GLOB.bitfields) + . = VV_BITFIELD + else + . = 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, /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 + . = VV_NULL + +/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes, var_name) + . = 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_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_BITFIELD) + .["value"] = input_bitfield(usr, "Editing bitfield: [var_name]", var_name, current_value) + 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_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 + var/atom/newguy = new type() + newguy.datum_flags |= DF_VAR_EDITED + .["value"] = newguy + + if (VV_NEW_DATUM) + var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) + if (!type) + .["class"] = null + return + .["type"] = type + var/datum/newguy = new type() + newguy.datum_flags |= DF_VAR_EDITED + .["value"] = newguy + + 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 + var/datum/newguy = new type() + if(istype(newguy)) + newguy.datum_flags |= DF_VAR_EDITED + .["value"] = newguy + + + 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) //hehe + + 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) == FALSE) + 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 its 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(objectvar, variable) + + to_chat(src, "Variable appears to be [uppertext(default)].") + + to_chat(src, "Variable contains: [variable]") + + if(default == VV_NUM) + var/dir_text = "" + var/tdir = variable + if(tdir > 0 && tdir < 16) + if(tdir & 1) + dir_text += "NORTH" + if(tdir & 2) + dir_text += "SOUTH" + if(tdir & 4) + dir_text += "EAST" + if(tdir & 8) + dir_text += "WEST" + + if(dir_text) + to_chat(usr, "If a direction, direction is: [dir_text]") + + var/original_var = variable + + 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) == FALSE) + 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 GLOB.VVlocked) + if(!check_rights(R_DEBUG)) + return FALSE + if(param_var_name in GLOB.VVckey_edit) + if(!check_rights(R_SPAWN|R_DEBUG)) + return FALSE + if(param_var_name in GLOB.VVicon_edit_lock) + if(!check_rights(R_FUN|R_DEBUG)) + return FALSE + if(param_var_name in GLOB.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 + + var_value = O.vars[variable] + if(!vv_varname_lockcheck(variable)) + return + if(istype(O, /datum/armor)) + var/prompt = alert(src, "Editing this var changes this value on potentially thousands of items that share the same combination of armor values. If you want to edit the armor of just one item, use the \"Modify armor values\" dropdown item", "DANGER", "ABORT ", "Continue", " ABORT") + if (prompt != "Continue") + return + + + var/default = vv_get_class(variable, 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(var_value > 0 && var_value < 16) + if(var_value & 1) + dir_text += "NORTH" + if(var_value & 2) + dir_text += "SOUTH" + if(var_value & 4) + dir_text += "EAST" + if(var_value & 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), var_name = variable) + 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) == FALSE) + to_chat(src, "Your edit was rejected by the object.") + return + vv_update_display(O, "varedited", VV_MSG_EDITED) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VAR_EDIT, args) + log_world("### VarEdit by [key_name(src)]: [O.type] [variable]=[var_value] => [var_new]") + log_admin("[key_name(src)] modified [original_name]'s [variable] from [html_encode("[var_value]")] to [html_encode("[var_new]")]") + var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] from [var_value] to [var_new]" + message_admins(msg) + admin_ticket_log(O, msg) return TRUE \ No newline at end of file diff --git a/tools/Redirector/textprocs.dm b/tools/Redirector/textprocs.dm index bd10fc3c..aedafe39 100644 --- a/tools/Redirector/textprocs.dm +++ b/tools/Redirector/textprocs.dm @@ -1,153 +1,153 @@ -/* - Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. - (2012) - - NOTE: The below functions are part of BYOND user Deadron's "TextHandling" library. - [ http://www.byond.com/developer/Deadron/TextHandling ] - */ - - -proc - /////////////////// - // Reading files // - /////////////////// - dd_file2list(file_path, separator = "\n") - var/file - if (isfile(file_path)) - file = file_path - else - file = file(file_path) - return dd_text2list(file2text(file), separator) - - - //////////////////// - // Replacing text // - //////////////////// - dd_replacetext(text, search_string, replacement_string) - // A nice way to do this is to split the text into an array based on the search_string, - // then put it back together into text using replacement_string as the new separator. - var/list/textList = dd_text2list(text, search_string) - return dd_list2text(textList, replacement_string) - - - dd_replaceText(text, search_string, replacement_string) - var/list/textList = dd_text2List(text, search_string) - return dd_list2text(textList, replacement_string) - - - ///////////////////// - // Prefix checking // - ///////////////////// - dd_hasprefix(text, prefix) - var/start = 1 - var/end = lentext(prefix) + 1 - return findtext(text, prefix, start, end) - - dd_hasPrefix(text, prefix) - var/start = 1 - var/end = lentext(prefix) + 1 - return findtextEx(text, prefix, start, end) - - - ///////////////////// - // Suffix checking // - ///////////////////// - dd_hassuffix(text, suffix) - var/start = length(text) - length(suffix) - if (start) - return findtext(text, suffix, start) - - dd_hasSuffix(text, suffix) - var/start = length(text) - length(suffix) - if (start) - return findtextEx(text, suffix, start) - - ///////////////////////////// - // Turning text into lists // - ///////////////////////////// - dd_text2list(text, separator) - var/textlength = lentext(text) - var/separatorlength = lentext(separator) - var/list/textList = new /list() - var/searchPosition = 1 - var/findPosition = 1 - var/buggyText - while (1) // Loop forever. - findPosition = findtext(text, separator, searchPosition, 0) - buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. - textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). - - searchPosition = findPosition + separatorlength // Skip over separator. - if (findPosition == 0) // Didn't find anything at end of string so stop here. - return textList - else - if (searchPosition > textlength) // Found separator at very end of string. - textList += "" // So add empty element. - return textList - - dd_text2List(text, separator) - var/textlength = lentext(text) - var/separatorlength = lentext(separator) - var/list/textList = new /list() - var/searchPosition = 1 - var/findPosition = 1 - var/buggyText - while (1) // Loop forever. - findPosition = findtextEx(text, separator, searchPosition, 0) - buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. - textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). - - searchPosition = findPosition + separatorlength // Skip over separator. - if (findPosition == 0) // Didn't find anything at end of string so stop here. - return textList - else - if (searchPosition > textlength) // Found separator at very end of string. - textList += "" // So add empty element. - return textList - - dd_list2text(list/the_list, separator) - var/total = the_list.len - if (total == 0) // Nothing to work with. - return - - var/newText = "[the_list[1]]" // Treats any object/number as text also. - var/count - for (count = 2, count <= total, count++) - if (separator) - newText += separator - newText += "[the_list[count]]" - return newText - - dd_centertext(message, length) - var/new_message = message - var/size = length(message) - if (size == length) - return new_message - if (size > length) - return copytext(new_message, 1, length + 1) - - // Need to pad text to center it. - var/delta = length - size - if (delta == 1) - // Add one space after it. - return new_message + " " - - // Is this an odd number? If so, add extra space to front. - if (delta % 2) - new_message = " " + new_message - delta-- - - // Divide delta in 2, add those spaces to both ends. - delta = delta / 2 - var/spaces = "" - for (var/count = 1, count <= delta, count++) - spaces += " " - return spaces + new_message + spaces - - dd_limittext(message, length) - // Truncates text to limit if necessary. - var/size = length(message) - if (size <= length) - return message - else +/* + Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. + (2012) + + NOTE: The below functions are part of BYOND user Deadron's "TextHandling" library. + [ http://www.byond.com/developer/Deadron/TextHandling ] + */ + + +proc + /////////////////// + // Reading files // + /////////////////// + dd_file2list(file_path, separator = "\n") + var/file + if (isfile(file_path)) + file = file_path + else + file = file(file_path) + return dd_text2list(file2text(file), separator) + + + //////////////////// + // Replacing text // + //////////////////// + dd_replacetext(text, search_string, replacement_string) + // A nice way to do this is to split the text into an array based on the search_string, + // then put it back together into text using replacement_string as the new separator. + var/list/textList = dd_text2list(text, search_string) + return dd_list2text(textList, replacement_string) + + + dd_replaceText(text, search_string, replacement_string) + var/list/textList = dd_text2List(text, search_string) + return dd_list2text(textList, replacement_string) + + + ///////////////////// + // Prefix checking // + ///////////////////// + dd_hasprefix(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtext(text, prefix, start, end) + + dd_hasPrefix(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtextEx(text, prefix, start, end) + + + ///////////////////// + // Suffix checking // + ///////////////////// + dd_hassuffix(text, suffix) + var/start = length(text) - length(suffix) + if (start) + return findtext(text, suffix, start) + + dd_hasSuffix(text, suffix) + var/start = length(text) - length(suffix) + if (start) + return findtextEx(text, suffix, start) + + ///////////////////////////// + // Turning text into lists // + ///////////////////////////// + dd_text2list(text, separator) + var/textlength = length(text) + var/separatorlength = length(separator) + var/list/textList = new /list() + var/searchPosition = 1 + var/findPosition = 1 + var/buggyText + while (1) // Loop forever. + findPosition = findtext(text, separator, searchPosition, 0) + buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. + textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). + + searchPosition = findPosition + separatorlength // Skip over separator. + if (findPosition == 0) // Didn't find anything at end of string so stop here. + return textList + else + if (searchPosition > textlength) // Found separator at very end of string. + textList += "" // So add empty element. + return textList + + dd_text2List(text, separator) + var/textlength = length(text) + var/separatorlength = length(separator) + var/list/textList = new /list() + var/searchPosition = 1 + var/findPosition = 1 + var/buggyText + while (1) // Loop forever. + findPosition = findtextEx(text, separator, searchPosition, 0) + buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. + textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). + + searchPosition = findPosition + separatorlength // Skip over separator. + if (findPosition == 0) // Didn't find anything at end of string so stop here. + return textList + else + if (searchPosition > textlength) // Found separator at very end of string. + textList += "" // So add empty element. + return textList + + dd_list2text(list/the_list, separator) + var/total = the_list.len + if (total == 0) // Nothing to work with. + return + + var/newText = "[the_list[1]]" // Treats any object/number as text also. + var/count + for (count = 2, count <= total, count++) + if (separator) + newText += separator + newText += "[the_list[count]]" + return newText + + dd_centertext(message, length) + var/new_message = message + var/size = length(message) + if (size == length) + return new_message + if (size > length) + return copytext(new_message, 1, length + 1) + + // Need to pad text to center it. + var/delta = length - size + if (delta == 1) + // Add one space after it. + return new_message + " " + + // Is this an odd number? If so, add extra space to front. + if (delta % 2) + new_message = " " + new_message + delta-- + + // Divide delta in 2, add those spaces to both ends. + delta = delta / 2 + var/spaces = "" + for (var/count = 1, count <= delta, count++) + spaces += " " + return spaces + new_message + spaces + + dd_limittext(message, length) + // Truncates text to limit if necessary. + var/size = length(message) + if (size <= length) + return message + else return copytext(message, 1, length + 1) \ No newline at end of file From 2899fabbc9d7f27402e988474e96cbfc5b36685e Mon Sep 17 00:00:00 2001 From: Archie Date: Tue, 5 Jan 2021 17:46:02 -0300 Subject: [PATCH 13/21] move mutations again --- code/datums/{mutations.dm => mutations/_mutations.dm} | 0 tgstation.dme | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename code/datums/{mutations.dm => mutations/_mutations.dm} (100%) diff --git a/code/datums/mutations.dm b/code/datums/mutations/_mutations.dm similarity index 100% rename from code/datums/mutations.dm rename to code/datums/mutations/_mutations.dm diff --git a/tgstation.dme b/tgstation.dme index 823b3b4a..3819cc83 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -324,7 +324,6 @@ #include "code\datums\martial.dm" #include "code\datums\mind.dm" #include "code\datums\mutable_appearance.dm" -#include "code\datums\mutations.dm" #include "code\datums\numbered_display.dm" #include "code\datums\outfit.dm" #include "code\datums\position_point_vector.dm" @@ -487,6 +486,7 @@ #include "code\datums\mood_events\generic_positive_events.dm" #include "code\datums\mood_events\mood_event.dm" #include "code\datums\mood_events\needs_events.dm" +#include "code\datums\mutations\_mutations.dm" #include "code\datums\mutations\actions.dm" #include "code\datums\mutations\antenna.dm" #include "code\datums\mutations\body.dm" From e17050ce27a795c7d3526190d1a5b08af966a904 Mon Sep 17 00:00:00 2001 From: CapnMachaddish Date: Wed, 6 Jan 2021 02:42:23 -0600 Subject: [PATCH 14/21] Allow TTS circuit to change its sayverb --- code/modules/integrated_electronics/subtypes/output.dm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm index 6005709d..ce9dbe5d 100644 --- a/code/modules/integrated_electronics/subtypes/output.dm +++ b/code/modules/integrated_electronics/subtypes/output.dm @@ -228,7 +228,7 @@ icon_state = "speaker" cooldown_per_use = 10 complexity = 12 - inputs = list("text" = IC_PINTYPE_STRING) + inputs = list("text" = IC_PINTYPE_STRING, "speech verb" = IC_PINTYPE_STRING) outputs = list() activators = list("to speech" = IC_PINTYPE_PULSE_IN) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH @@ -236,14 +236,18 @@ /obj/item/integrated_circuit/output/text_to_speech/do_work() text = get_pin_data(IC_INPUT, 1) + var/say_verb = get_pin_data(IC_INPUT, 2) if(!isnull(text)) var/atom/movable/A = get_object() var/sanitized_text = sanitize(text) - A.say(sanitized_text) + var/sanitized_verb = sanitize(say_verb) if (assembly) + if(!isnull(sanitized_verb)) + A.verb_say = sanitized_verb log_say("[assembly] [REF(assembly)] : [sanitized_text]") else log_say("[name] ([type]) : [sanitized_text]") + A.say(sanitized_text) /obj/item/integrated_circuit/output/video_camera name = "video camera circuit" From ca735a752e21dc95df0c9fb3b3914425bf8bf322 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Sat, 11 Jan 2020 15:21:08 +0100 Subject: [PATCH 15/21] Merge pull request #10505 from LetterN/beep-boom better electropack ui --- code/controllers/subsystem/pai.dm | 2 +- .../items/devices/radio/electropack.dm | 290 +++++++++--------- .../items/devices/radio/shockcollar.dm | 62 ++-- 3 files changed, 175 insertions(+), 179 deletions(-) diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm index 2e2f7edd..8f95f2ec 100644 --- a/code/controllers/subsystem/pai.dm +++ b/code/controllers/subsystem/pai.dm @@ -8,7 +8,7 @@ SUBSYSTEM_DEF(pai) var/spam_delay = 100 var/list/pai_card_list = list() -/datum/controller/subsystem/pai/Topic(href, href_list[]) +/datum/controller/subsystem/pai/Topic(href, href_list) if(href_list["download"]) var/datum/paiCandidate/candidate = locate(href_list["candidate"]) in candidates var/obj/item/paicard/card = locate(href_list["device"]) in pai_card_list diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index 967fd3fb..f4681210 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -1,144 +1,146 @@ -/obj/item/electropack - name = "electropack" - desc = "Dance my monkeys! DANCE!!!" - icon = 'icons/obj/radio.dmi' - icon_state = "electropack0" - item_state = "electropack" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - w_class = WEIGHT_CLASS_HUGE - materials = list(MAT_METAL=10000, MAT_GLASS=2500) - var/on = TRUE - var/code = 2 - var/frequency = FREQ_ELECTROPACK - var/shock_cooldown = 0 - -/obj/item/electropack/suicide_act(mob/user) - user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -/obj/item/electropack/Initialize() - . = ..() - SSradio.add_object(src, frequency, RADIO_SIGNALER) - -/obj/item/electropack/Destroy() - SSradio.remove_object(src, frequency) - return ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/electropack/attack_hand(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.back) - to_chat(user, "You need help taking this off!") - return - return ..() - -/obj/item/electropack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/clothing/head/helmet)) - var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) - A.icon = 'icons/obj/assemblies.dmi' - - if(!user.transferItemToLoc(W, A)) - to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") - return - W.master = A - A.part1 = W - - user.transferItemToLoc(src, A, TRUE) - master = A - A.part2 = src - - user.put_in_hands(A) - A.add_fingerprint(user) - else - return ..() - -/obj/item/electropack/Topic(href, href_list) - //..() - var/mob/living/carbon/C = usr - if(usr.stat || usr.restrained() || C.back == src) - return - if((ishuman(usr) && usr.contents.Find(src)) || usr.contents.Find(master) || (in_range(src, usr) && isturf(loc))) - usr.set_machine(src) - if(href_list["freq"]) - SSradio.remove_object(src, frequency) - frequency = sanitize_frequency(frequency + text2num(href_list["freq"])) - SSradio.add_object(src, frequency, RADIO_SIGNALER) - else - if(href_list["code"]) - code += text2num(href_list["code"]) - code = round(code) - code = min(100, code) - code = max(1, code) - else - if(href_list["power"]) - on = !( on ) - icon_state = "electropack[on]" - if(!( master )) - if(ismob(loc)) - attack_self(loc) - else - for(var/mob/M in viewers(1, src)) - if(M.client) - attack_self(M) - else - if(ismob(master.loc)) - attack_self(master.loc) - else - for(var/mob/M in viewers(1, master)) - if(M.client) - attack_self(M) - else - usr << browse(null, "window=radio") - return - return - -/obj/item/electropack/receive_signal(datum/signal/signal) - if(!signal || signal.data["code"] != code) - return - - if(isliving(loc) && on) - if(shock_cooldown != 0) - return - shock_cooldown = 1 - addtimer(VARSET_CALLBACK(src, shock_cooldown, 0), 100) - var/mob/living/L = loc - step(L, pick(GLOB.cardinals)) - - to_chat(L, "You feel a sharp shock!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, L) - s.start() - - L.Knockdown(100) - - if(master) - master.receive_signal() - return - -/obj/item/electropack/attack_self(mob/user) - - if(!ishuman(user)) - return - user.set_machine(src) - var/dat = {"Turned [on ? "On" : "Off"] - -Toggle
    -Frequency/Code for electropack:
    -Frequency: -- -- [format_frequency(frequency)] -+ -+
    - -Code: -- -- [code] -+ -+
    -
    "} - user << browse(dat, "window=radio") - onclose(user, "radio") - return +/obj/item/electropack + name = "electropack" + desc = "Dance my monkeys! DANCE!!!" + icon = 'icons/obj/radio.dmi' + icon_state = "electropack0" + item_state = "electropack" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_HUGE + materials = list(MAT_METAL=10000, MAT_GLASS=2500) + + var/code = 2 + var/frequency = FREQ_ELECTROPACK + var/on = TRUE + var/shock_cooldown = FALSE + +/obj/item/electropack/suicide_act(mob/living/carbon/user) + user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +/obj/item/electropack/Initialize() + . = ..() + set_frequency(frequency) + +/obj/item/electropack/Destroy() + SSradio.remove_object(src, frequency) + . = ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/electropack/attack_hand(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.back) + to_chat(user, "You need help taking this off!") + return + return ..() + +/obj/item/electropack/attackby(obj/item/W, mob/living/user, params) + if(istype(W, /obj/item/clothing/head/helmet)) + var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) + A.icon = 'icons/obj/assemblies.dmi' + + if(!user.transferItemToLoc(W, A)) + to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") + return + W.master = A + A.part1 = W + + user.transferItemToLoc(src, A, TRUE) + master = A + A.part2 = src + + user.put_in_hands(A) + A.add_fingerprint(user) + else + return ..() + +/obj/item/electropack/Topic(href, href_list) + var/mob/living/carbon/C = usr + if(usr.stat || usr.restrained() || C.back == src) + return + + if(!usr.canUseTopic(src, BE_CLOSE)) + usr << browse(null, "window=radio") + onclose(usr, "radio") + return + + if(href_list["set"]) + if(href_list["set"] == "freq") + var/new_freq = input(usr, "Input a new receiving frequency", "Electropack Frequency", format_frequency(frequency)) as num|null + if(!usr.canUseTopic(src, BE_CLOSE)) + return + new_freq = unformat_frequency(new_freq) + new_freq = sanitize_frequency(new_freq, TRUE) + set_frequency(new_freq) + + if(href_list["set"] == "code") + var/new_code = input(usr, "Input a new receiving code", "Electropack Code", code) as num|null + if(!usr.canUseTopic(src, BE_CLOSE)) + return + new_code = round(new_code) + new_code = CLAMP(new_code, 1, 100) + code = new_code + + if(href_list["set"] == "power") + if(!usr.canUseTopic(src, BE_CLOSE)) + return + on = !(on) + icon_state = "electropack[on]" + + if(usr) + attack_self(usr) + + return + +/obj/item/electropack/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + SSradio.add_object(src, frequency, RADIO_SIGNALER) + return + +/obj/item/electropack/receive_signal(datum/signal/signal) + if(!signal || signal.data["code"] != code) + return + + if(isliving(loc) && on) + if(shock_cooldown == TRUE) + return + shock_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100) + var/mob/living/L = loc + step(L, pick(GLOB.cardinals)) + + to_chat(L, "You feel a sharp shock!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, L) + s.start() + + L.Knockdown(100) + + if(master) + master.receive_signal() + return + +/obj/item/electropack/ui_interact(mob/user) + if(!ishuman(user)) + return + + user.set_machine(src) + var/dat = {" + +Turned [on ? "On" : "Off"] - Toggle
    +Frequency/Code for electropack:
    +Frequency: +[format_frequency(src.frequency)] +Set
    + +Code: +[src.code] +Set
    +
    "} + user << browse(dat, "window=radio") + onclose(user, "radio") + return diff --git a/modular_citadel/code/game/objects/items/devices/radio/shockcollar.dm b/modular_citadel/code/game/objects/items/devices/radio/shockcollar.dm index fe5fcc0e..ff4b403b 100644 --- a/modular_citadel/code/game/objects/items/devices/radio/shockcollar.dm +++ b/modular_citadel/code/game/objects/items/devices/radio/shockcollar.dm @@ -11,6 +11,7 @@ strip_delay = 60 equip_delay_other = 60 materials = list(MAT_METAL=5000, MAT_GLASS=2000) + var/tagname = null /datum/design/electropack/shockcollar @@ -25,18 +26,17 @@ if(loc == user && user.get_item_by_slot(SLOT_NECK)) to_chat(user, "The collar is fastened tight! You'll need help taking this off!") return - ..() + return ..() /obj/item/electropack/shockcollar/receive_signal(datum/signal/signal) if(!signal || signal.data["code"] != code) return if(isliving(loc) && on) - if(shock_cooldown != 0) + if(shock_cooldown == TRUE) return - shock_cooldown = 1 - spawn(100) - shock_cooldown = 0 + shock_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100) var/mob/living/L = loc step(L, pick(GLOB.cardinals)) @@ -51,36 +51,30 @@ master.receive_signal() return -/obj/item/electropack/shockcollar/attack_self(mob/user) //Turns out can't fully source this from the parent item, spritepath gets confused if power toggled. Will come back to this when I know how to code better and readd powertoggle.. - var/option = "Change Name" - option = input(user, "What do you want to do?", "[src]", option) as null|anything in list("Change Name", "Change Frequency") - switch(option) - if("Change Name") - var/t = input(user, "Would you like to change the name on the tag?", "Name your new pet", tagname ? tagname : "Spot") as null|text - if(t) - tagname = copytext(sanitize(t), 1, MAX_NAME_LEN) - name = "[initial(name)] - [tagname]" - if("Change Frequency") - if(!ishuman(user)) - return - user.set_machine(src) - var/dat = {"
    - Frequency/Code for shock collar:
    - Frequency: - - - - [format_frequency(frequency)] - + - +
    - Code: - - - - [code] - + - +
    -
    "} +/obj/item/electropack/shockcollar/attackby(obj/item/W, mob/user, params) //moves it here because on_click is being bad + if(istype(W, /obj/item/pen)) + var/t = input(user, "Would you like to change the name on the tag?", "Name your new pet", tagname ? tagname : "Spot") as null|text + if(t) + tagname = copytext(sanitize(t), 1, MAX_NAME_LEN) + name = "[initial(name)] - [tagname]" + else + return ..() - user << browse(dat, "window=radio") - onclose(user, "radio") - return +/obj/item/electropack/shockcollar/ui_interact(mob/user) //on_click calls this + var/dat = {" + +Frequency/Code for shock collar:
    +Frequency: +[format_frequency(src.frequency)] +Set
    + +Code: +[src.code] +Set
    +
    "} + user << browse(dat, "window=radio") + onclose(user, "radio") + return /obj/item/electropack/shockcollar/security name = "security shock collar" From 02da471330484982e2ef9f7f9024941c0ae864d9 Mon Sep 17 00:00:00 2001 From: Lin Date: Fri, 3 Jan 2020 00:01:54 +0000 Subject: [PATCH 16/21] Merge pull request #10238 from Ghommie/Ghommie-cit478 Brings in some mapping code updates. --- _maps/RandomZLevels/Cabin.dmm | 22 +- _maps/RandomZLevels/VR/snowdin_VR.dmm | 26 +- _maps/RandomZLevels/caves.dmm | 6 +- _maps/RandomZLevels/moonoutpost19.dmm | 6 +- _maps/RandomZLevels/snowdin.dmm | 26 +- _maps/RandomZLevels/spacebattle.dmm | 30 +- _maps/RandomZLevels/undergroundoutpost45.dmm | 9 +- _maps/map_files/Mining/Lavaland.dmm | 44 +- code/__DEFINES/atmospherics.dm | 9 +- code/__DEFINES/maps.dm | 5 +- code/__HELPERS/_logging.dm | 2 + code/__HELPERS/level_traits.dm | 3 - code/_globalvars/logging.dm | 122 +-- code/controllers/subsystem/air.dm | 2 +- code/game/atoms.dm | 2 +- code/game/machinery/doors/airlock.dm | 2 +- code/game/objects/items/stunbaton.dm | 502 ++++++------ code/game/turfs/baseturf_skipover.dm | 7 +- code/game/turfs/change_turf.dm | 23 +- code/game/turfs/simulated/floor/plating.dm | 2 +- .../turfs/simulated/floor/plating/asteroid.dm | 8 +- code/game/turfs/turf.dm | 2 +- code/game/world.dm | 665 ++++++++-------- code/modules/admin/verbs/mapping.dm | 735 +++++++++--------- .../atmospherics/machinery/datum_pipeline.dm | 4 +- .../atmospherics/machinery/other/meter.dm | 294 +++---- code/modules/holodeck/computer.dm | 4 +- code/modules/mapping/map_template.dm | 11 +- code/modules/mapping/mapping_helpers.dm | 30 +- code/modules/mapping/preloader.dm | 20 +- code/modules/mapping/reader.dm | 8 +- code/modules/recycling/conveyor2.dm | 2 +- 32 files changed, 1310 insertions(+), 1323 deletions(-) diff --git a/_maps/RandomZLevels/Cabin.dmm b/_maps/RandomZLevels/Cabin.dmm index f4a18fd8..d2c0ca6f 100644 --- a/_maps/RandomZLevels/Cabin.dmm +++ b/_maps/RandomZLevels/Cabin.dmm @@ -205,16 +205,16 @@ /area/awaymission/cabin) "aJ" = ( /obj/machinery/shower{ - icon_state = "shower"; - dir = 8 + dir = 8; + icon_state = "shower" }, /obj/machinery/door/window/northright, /turf/open/floor/plasteel/freezer, /area/awaymission/cabin) "aK" = ( /obj/machinery/shower{ - icon_state = "shower"; - dir = 4 + dir = 4; + icon_state = "shower" }, /obj/machinery/door/window/northleft, /turf/open/floor/plasteel/freezer, @@ -466,13 +466,13 @@ /area/awaymission/cabin) "bI" = ( /obj/machinery/shower{ - icon_state = "shower"; - dir = 4 + dir = 4; + icon_state = "shower" }, /obj/machinery/door/window/eastright, /obj/structure/window{ - icon_state = "window"; - dir = 1 + dir = 1; + icon_state = "window" }, /turf/open/floor/plasteel/freezer, /area/awaymission/cabin) @@ -967,10 +967,6 @@ }, /turf/open/floor/plating, /area/awaymission/cabin) -"dw" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock/snow, -/area/space) (1,1,1) = {" aa @@ -1227,7 +1223,7 @@ aa aa aa aa -dw +aa "} (2,1,1) = {" aa diff --git a/_maps/RandomZLevels/VR/snowdin_VR.dmm b/_maps/RandomZLevels/VR/snowdin_VR.dmm index 0e67b722..22261561 100644 --- a/_maps/RandomZLevels/VR/snowdin_VR.dmm +++ b/_maps/RandomZLevels/VR/snowdin_VR.dmm @@ -4,9 +4,15 @@ /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) "ab" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock/snow, -/area/awaymission/snowdin/cave/mountain) +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/mob/living/simple_animal/hostile/netherworld/migo, +/turf/open/floor/plasteel, +/area/awaymission/snowdin/post/mining_dock) "ac" = ( /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) @@ -10267,16 +10273,6 @@ /obj/item/shard, /turf/open/floor/plating, /area/awaymission/snowdin/post/mining_dock) -"xA" = ( -/mob/living/simple_animal/hostile/netherworld/migo, -/obj/effect/turf_decal/tile/neutral{ - dir = 1 - }, -/obj/effect/turf_decal/tile/neutral{ - dir = 8 - }, -/turf/open/floor/plasteel, -/area/awaymission/snowdin/post/mining_dock) "xB" = ( /obj/effect/turf_decal/stripes/line{ dir = 4 @@ -16013,7 +16009,7 @@ ac ac "} (2,1,1) = {" -ab +ac ac ac ac @@ -66518,7 +66514,7 @@ wD wT xe xs -xA +ab xI xN wL diff --git a/_maps/RandomZLevels/caves.dmm b/_maps/RandomZLevels/caves.dmm index 92373463..913de081 100644 --- a/_maps/RandomZLevels/caves.dmm +++ b/_maps/RandomZLevels/caves.dmm @@ -2251,10 +2251,6 @@ initial_gas_mix = "n2=23;o2=14" }, /area/awaymission/caves/BMP_asteroid) -"gW" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock, -/area/space/nearstation) "gX" = ( /obj/effect/baseturf_helper/lava, /turf/closed/mineral/volcanic, @@ -2536,7 +2532,7 @@ aa aa aa aa -gW +aa "} (2,1,1) = {" aa diff --git a/_maps/RandomZLevels/moonoutpost19.dmm b/_maps/RandomZLevels/moonoutpost19.dmm index c251a338..837adbf7 100644 --- a/_maps/RandomZLevels/moonoutpost19.dmm +++ b/_maps/RandomZLevels/moonoutpost19.dmm @@ -7212,10 +7212,6 @@ heat_capacity = 1e+006 }, /area/awaymission/moonoutpost19/research) -"oV" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/open/space, -/area/space) "vV" = ( /obj/machinery/door/airlock/external, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ @@ -7509,7 +7505,7 @@ aa aa aa aa -oV +aa "} (2,1,1) = {" aa diff --git a/_maps/RandomZLevels/snowdin.dmm b/_maps/RandomZLevels/snowdin.dmm index 354b8725..f81e6a1f 100644 --- a/_maps/RandomZLevels/snowdin.dmm +++ b/_maps/RandomZLevels/snowdin.dmm @@ -4,9 +4,15 @@ /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) "ab" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock/snow, -/area/awaymission/snowdin/cave/mountain) +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/mob/living/simple_animal/hostile/netherworld/migo, +/turf/open/floor/plasteel, +/area/awaymission/snowdin/post/mining_dock) "ac" = ( /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) @@ -10361,16 +10367,6 @@ /obj/item/shard, /turf/open/floor/plating, /area/awaymission/snowdin/post/mining_dock) -"xA" = ( -/mob/living/simple_animal/hostile/netherworld/migo, -/obj/effect/turf_decal/tile/neutral{ - dir = 1 - }, -/obj/effect/turf_decal/tile/neutral{ - dir = 8 - }, -/turf/open/floor/plasteel, -/area/awaymission/snowdin/post/mining_dock) "xB" = ( /obj/effect/turf_decal/stripes/line{ dir = 4 @@ -16211,7 +16207,7 @@ ac ac "} (2,1,1) = {" -ab +ac ac ac ac @@ -66716,7 +66712,7 @@ wD wT xe xs -xA +ab xI xN wL diff --git a/_maps/RandomZLevels/spacebattle.dmm b/_maps/RandomZLevels/spacebattle.dmm index 21cd37f7..66d7f556 100644 --- a/_maps/RandomZLevels/spacebattle.dmm +++ b/_maps/RandomZLevels/spacebattle.dmm @@ -5,6 +5,16 @@ "ab" = ( /turf/open/space, /area/space) +"ac" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/mob/living/simple_animal/hostile/syndicate/melee/sword, +/turf/open/floor/plasteel, +/area/awaymission/spacebattle/cruiser) "ad" = ( /obj/structure/shuttle/engine/propulsion/right{ dir = 1 @@ -2597,16 +2607,6 @@ /obj/effect/spawner/lootdrop/armory_contraband, /turf/open/floor/plating, /area/awaymission/spacebattle/cruiser) -"jK" = ( -/mob/living/simple_animal/hostile/syndicate/melee/sword, -/obj/effect/turf_decal/tile/blue{ - dir = 1 - }, -/obj/effect/turf_decal/tile/blue{ - dir = 8 - }, -/turf/open/floor/plasteel, -/area/awaymission/spacebattle/cruiser) "jL" = ( /obj/machinery/door/poddoor{ id = "spacebattlearmory"; @@ -2894,10 +2894,6 @@ /obj/item/mecha_parts/mecha_equipment/weapon/energy/ion, /turf/open/floor/plating, /area/awaymission/spacebattle/cruiser) -"kM" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/mineral/random, -/area/space/nearstation) "vw" = ( /turf/closed/wall/mineral/plastitanium/nodiagonal, /area/awaymission/spacebattle/syndicate5) @@ -3175,7 +3171,7 @@ aa aa aa aa -kM +aa "} (2,1,1) = {" aa @@ -35686,10 +35682,10 @@ eC cn fL cp -jK +ac fs fO -jK +ac eM eM eM diff --git a/_maps/RandomZLevels/undergroundoutpost45.dmm b/_maps/RandomZLevels/undergroundoutpost45.dmm index f6d46ef9..56018b94 100644 --- a/_maps/RandomZLevels/undergroundoutpost45.dmm +++ b/_maps/RandomZLevels/undergroundoutpost45.dmm @@ -25,6 +25,9 @@ "ag" = ( /turf/closed/wall/mineral/titanium, /area/awaymission/undergroundoutpost45/central) +"ah" = ( +/turf/open/space, +/area/space/nearstation) "aj" = ( /obj/effect/decal/cleanable/dirt, /turf/open/floor/plasteel{ @@ -13945,10 +13948,6 @@ temperature = 363.9 }, /area/awaymission/undergroundoutpost45/caves) -"zi" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/open/space, -/area/space/nearstation) "KE" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 4 @@ -14211,7 +14210,7 @@ aa aa aa aa -zi +ah "} (2,1,1) = {" aa diff --git a/_maps/map_files/Mining/Lavaland.dmm b/_maps/map_files/Mining/Lavaland.dmm index d9e45bf4..cbce562a 100644 --- a/_maps/map_files/Mining/Lavaland.dmm +++ b/_maps/map_files/Mining/Lavaland.dmm @@ -4069,10 +4069,6 @@ }, /turf/open/floor/plating, /area/mine/laborcamp) -"ut" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/laborcamp) "uM" = ( /obj/machinery/light/small{ dir = 8 @@ -4294,10 +4290,6 @@ /obj/structure/ore_box, /turf/open/floor/plating/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) -"Ay" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/laborcamp/security) "AH" = ( /obj/machinery/computer/secure_data{ dir = 1 @@ -4358,10 +4350,6 @@ }, /turf/open/floor/plasteel, /area/mine/production) -"BU" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/eva) "BW" = ( /obj/effect/spawner/structure/window/reinforced, /turf/open/floor/plating, @@ -4380,10 +4368,6 @@ /obj/structure/sign/warning/electricshock, /turf/closed/wall, /area/mine/living_quarters) -"Df" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/production) "Di" = ( /obj/structure/reagent_dispensers/fueltank, /turf/open/floor/plating, @@ -4613,10 +4597,6 @@ "PQ" = ( /turf/closed/wall, /area/mine/living_quarters) -"Qk" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/living_quarters) "QZ" = ( /turf/open/floor/plating/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors/unexplored/danger) @@ -4626,10 +4606,6 @@ "Rg" = ( /turf/open/floor/plating/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors/unexplored) -"Ro" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/open/lava/smooth/lava_land_surface, -/area/lavaland/surface/outdoors) "Sd" = ( /turf/open/floor/plasteel, /area/mine/living_quarters) @@ -4648,10 +4624,6 @@ "Wp" = ( /turf/open/lava/smooth/lava_land_surface, /area/lavaland/surface/outdoors) -"WJ" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall/r_wall, -/area/mine/maintenance) "Xs" = ( /turf/closed/mineral/random/volcanic, /area/lavaland/surface/outdoors) @@ -4923,7 +4895,7 @@ Wp Wp Wp Wp -Ro +Wp "} (2,1,1) = {" Zx @@ -9768,7 +9740,7 @@ Py rN Py tz -ut +Py vd Py yu @@ -10029,7 +10001,7 @@ nh nh Py yw -Ay +yw yw Wp Wp @@ -13381,7 +13353,7 @@ cX cX PQ cQ -Qk +PQ cX cX bQ @@ -18518,7 +18490,7 @@ Wp em XJ Fx -WJ +em dk er PQ @@ -21844,7 +21816,7 @@ pa wh ak at -BU +ob aH pa wh @@ -22365,7 +22337,7 @@ ob wv bj wv -Df +dN Gq GN GN @@ -22618,7 +22590,7 @@ aI aV bc bi -BU +ob bu bE bZ diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index eeb56608..4a73523a 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -149,6 +149,13 @@ #define CANATMOSPASS(A, O) ( A.CanAtmosPass == ATMOS_PASS_PROC ? A.CanAtmosPass(O) : ( A.CanAtmosPass == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPass ) ) #define CANVERTICALATMOSPASS(A, O) ( A.CanAtmosPassVertical == ATMOS_PASS_PROC ? A.CanAtmosPass(O, TRUE) : ( A.CanAtmosPassVertical == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPassVertical ) ) +//OPEN TURF ATMOS +#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" //the default air mix that open turfs spawn +#define TCOMMS_ATMOS "n2=100;TEMP=80" //-193,15degC telecommunications. also used for xenobiology slime killrooms +#define AIRLESS_ATMOS "TEMP=2.7" //space +#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" //-93.15degC snow and ice turfs +#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" //used in the holodeck burn test program + //LAVALAND #define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 50 //what pressure you have to be under to increase the effect of equipment meant for lavaland #define LAVALAND_DEFAULT_ATMOS "o2=14;n2=23;TEMP=300" @@ -299,4 +306,4 @@ GLOBAL_LIST_INIT(pipe_paint_colors, list( )) #define MIASMA_CORPSE_MOLES 0.02 -#define MIASMA_GIBS_MOLES 0.005 \ No newline at end of file +#define MIASMA_GIBS_MOLES 0.005 diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm index 3288c96a..d6f1097a 100644 --- a/code/__DEFINES/maps.dm +++ b/code/__DEFINES/maps.dm @@ -39,8 +39,6 @@ require only minor tweaks. #define ZTRAIT_LAVA_RUINS "Lava Ruins" #define ZTRAIT_LAVA_UNDERGROUND "Lava Underground" #define ZTRAIT_ISOLATED_RUINS "Isolated Ruins" //Placing ruins on z levels with this trait will use turf reservation instead of usual placement. -// prevents certain turfs from being stripped by a singularity -#define ZTRAIT_PLANET "Planet" // number - bombcap is multiplied by this before being applied to bombs #define ZTRAIT_BOMBCAP_MULTIPLIER "Bombcap Multiplier" @@ -61,6 +59,9 @@ require only minor tweaks. // CROSSLINKED - mixed in with the cross-linked space pool #define CROSSLINKED "Cross" +// string - type path of the z-level's baseturf (defaults to space) +#define ZTRAIT_BASETURF "Baseturf" + // default trait definitions, used by SSmapping #define ZTRAITS_CENTCOM list(ZTRAIT_CENTCOM = TRUE) #define ZTRAITS_STATION list(ZTRAIT_LINKAGE = CROSSLINKED, ZTRAIT_STATION = TRUE) diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 774b8768..928346d5 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -160,6 +160,8 @@ WRITE_LOG(GLOB.config_error_log, text) SEND_TEXT(world.log, text) +/proc/log_mapping(text) + WRITE_LOG(GLOB.world_map_error_log, text) /* For logging round startup. */ /proc/start_log(log) diff --git a/code/__HELPERS/level_traits.dm b/code/__HELPERS/level_traits.dm index 55ee0693..3e6e88c8 100644 --- a/code/__HELPERS/level_traits.dm +++ b/code/__HELPERS/level_traits.dm @@ -12,6 +12,3 @@ #define is_reserved_level(z) SSmapping.level_trait(z, ZTRAIT_RESERVED) #define is_away_level(z) SSmapping.level_trait(z, ZTRAIT_AWAY) - -// If true, the singularity cannot strip away asteroid turf on this Z -#define is_planet_level(z) SSmapping.level_trait(z, ZTRAIT_PLANET) diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 485e67e2..5ca3513e 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -1,60 +1,62 @@ -GLOBAL_VAR(log_directory) -GLOBAL_PROTECT(log_directory) -GLOBAL_VAR(world_game_log) -GLOBAL_PROTECT(world_game_log) -GLOBAL_VAR(world_runtime_log) -GLOBAL_PROTECT(world_runtime_log) -GLOBAL_VAR(world_qdel_log) -GLOBAL_PROTECT(world_qdel_log) -GLOBAL_VAR(world_attack_log) -GLOBAL_PROTECT(world_attack_log) -GLOBAL_VAR(world_href_log) -GLOBAL_PROTECT(world_href_log) -GLOBAL_VAR(round_id) -GLOBAL_PROTECT(round_id) -GLOBAL_VAR(config_error_log) -GLOBAL_PROTECT(config_error_log) -GLOBAL_VAR(sql_error_log) -GLOBAL_PROTECT(sql_error_log) -GLOBAL_VAR(world_pda_log) -GLOBAL_PROTECT(world_pda_log) -GLOBAL_VAR(world_telecomms_log) -GLOBAL_PROTECT(world_telecomms_log) -GLOBAL_VAR(world_manifest_log) -GLOBAL_PROTECT(world_manifest_log) -GLOBAL_VAR(query_debug_log) -GLOBAL_PROTECT(query_debug_log) -GLOBAL_VAR(world_job_debug_log) -GLOBAL_PROTECT(world_job_debug_log) -GLOBAL_VAR(world_virus_log) -GLOBAL_PROTECT(world_virus_log) - -GLOBAL_LIST_EMPTY(bombers) -GLOBAL_PROTECT(bombers) -GLOBAL_LIST_EMPTY(admin_log) -GLOBAL_PROTECT(admin_log) -GLOBAL_LIST_EMPTY(lastsignalers) //keeps last 100 signals here in format: "[src] used [REF(src)] @ location [src.loc]: [freq]/[code]" -GLOBAL_PROTECT(lastsignalers) -GLOBAL_LIST_EMPTY(lawchanges) //Stores who uploaded laws to which silicon-based lifeform, and what the law was -GLOBAL_PROTECT(lawchanges) - -GLOBAL_LIST_EMPTY(combatlog) -GLOBAL_PROTECT(combatlog) -GLOBAL_LIST_EMPTY(IClog) -GLOBAL_PROTECT(IClog) -GLOBAL_LIST_EMPTY(OOClog) -GLOBAL_PROTECT(OOClog) -GLOBAL_LIST_EMPTY(adminlog) -GLOBAL_PROTECT(adminlog) - -GLOBAL_LIST_EMPTY(active_turfs_startlist) - -/////Picture logging -GLOBAL_VAR(picture_log_directory) -GLOBAL_PROTECT(picture_log_directory) - -GLOBAL_VAR_INIT(picture_logging_id, 1) -GLOBAL_PROTECT(picture_logging_id) -GLOBAL_VAR(picture_logging_prefix) -GLOBAL_PROTECT(picture_logging_prefix) -///// +GLOBAL_VAR(log_directory) +GLOBAL_PROTECT(log_directory) +GLOBAL_VAR(world_game_log) +GLOBAL_PROTECT(world_game_log) +GLOBAL_VAR(world_runtime_log) +GLOBAL_PROTECT(world_runtime_log) +GLOBAL_VAR(world_qdel_log) +GLOBAL_PROTECT(world_qdel_log) +GLOBAL_VAR(world_attack_log) +GLOBAL_PROTECT(world_attack_log) +GLOBAL_VAR(world_href_log) +GLOBAL_PROTECT(world_href_log) +GLOBAL_VAR(round_id) +GLOBAL_PROTECT(round_id) +GLOBAL_VAR(config_error_log) +GLOBAL_PROTECT(config_error_log) +GLOBAL_VAR(sql_error_log) +GLOBAL_PROTECT(sql_error_log) +GLOBAL_VAR(world_pda_log) +GLOBAL_PROTECT(world_pda_log) +GLOBAL_VAR(world_telecomms_log) +GLOBAL_PROTECT(world_telecomms_log) +GLOBAL_VAR(world_manifest_log) +GLOBAL_PROTECT(world_manifest_log) +GLOBAL_VAR(query_debug_log) +GLOBAL_PROTECT(query_debug_log) +GLOBAL_VAR(world_job_debug_log) +GLOBAL_PROTECT(world_job_debug_log) +GLOBAL_VAR(world_virus_log) +GLOBAL_PROTECT(world_virus_log) +GLOBAL_VAR(world_map_error_log) +GLOBAL_PROTECT(world_map_error_log) + +GLOBAL_LIST_EMPTY(bombers) +GLOBAL_PROTECT(bombers) +GLOBAL_LIST_EMPTY(admin_log) +GLOBAL_PROTECT(admin_log) +GLOBAL_LIST_EMPTY(lastsignalers) //keeps last 100 signals here in format: "[src] used [REF(src)] @ location [src.loc]: [freq]/[code]" +GLOBAL_PROTECT(lastsignalers) +GLOBAL_LIST_EMPTY(lawchanges) //Stores who uploaded laws to which silicon-based lifeform, and what the law was +GLOBAL_PROTECT(lawchanges) + +GLOBAL_LIST_EMPTY(combatlog) +GLOBAL_PROTECT(combatlog) +GLOBAL_LIST_EMPTY(IClog) +GLOBAL_PROTECT(IClog) +GLOBAL_LIST_EMPTY(OOClog) +GLOBAL_PROTECT(OOClog) +GLOBAL_LIST_EMPTY(adminlog) +GLOBAL_PROTECT(adminlog) + +GLOBAL_LIST_EMPTY(active_turfs_startlist) + +/////Picture logging +GLOBAL_VAR(picture_log_directory) +GLOBAL_PROTECT(picture_log_directory) + +GLOBAL_VAR_INIT(picture_logging_id, 1) +GLOBAL_PROTECT(picture_logging_id) +GLOBAL_VAR(picture_logging_prefix) +GLOBAL_PROTECT(picture_logging_prefix) +///// diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 1bfdc79d..2a59558b 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -315,7 +315,7 @@ SUBSYSTEM_DEF(air) var/starting_ats = active_turfs.len sleep(world.tick_lag) var/timer = world.timeofday - warning("There are [starting_ats] active turfs at roundstart, this is a mapping error caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required)") + log_mapping("There are [starting_ats] active turfs at roundstart caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required).") for(var/turf/T in active_turfs) GLOB.active_turfs_startlist += T diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 00614f91..e40fabd9 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -53,7 +53,7 @@ /atom/New(loc, ...) //atom creation method that preloads variables at creation if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() - GLOB._preloader.load(src) + world.preloader_load(src) if(datum_flags & DF_USE_TAG) GenerateTag() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 7a2f1fd3..c31c52c5 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -179,7 +179,7 @@ limit-- while(!FoundDoor && limit) if (!FoundDoor) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] failed to find a valid airlock to cyclelink with!") + log_mapping("[src] at [AREACOORD(src)] failed to find a valid airlock to cyclelink with!") return FoundDoor.cyclelinkedairlock = src cyclelinkedairlock = FoundDoor diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index a2435d6f..3a2b9d57 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -1,251 +1,251 @@ -#define STUNBATON_CHARGE_LENIENCY 0.3 - -/obj/item/melee/baton - name = "stunbaton" - desc = "A stun baton for incapacitating people with." - icon_state = "stunbaton" - item_state = "baton" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - slot_flags = ITEM_SLOT_BELT - force = 10 - throwforce = 7 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("beaten") - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - - var/stunforce = 140 - var/status = FALSE - var/obj/item/stock_parts/cell/cell - var/hitcost = 1000 - var/throw_hit_chance = 35 - var/preload_cell_type //if not empty the baton starts with this type of cell - -/obj/item/melee/baton/get_cell() - . = cell - if(iscyborg(loc)) - var/mob/living/silicon/robot/R = loc - . = R.get_cell() - -/obj/item/melee/baton/suicide_act(mob/user) - user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -/obj/item/melee/baton/Initialize() - . = ..() - if(preload_cell_type) - if(!ispath(preload_cell_type,/obj/item/stock_parts/cell)) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") - else - cell = new preload_cell_type(src) - update_icon() - -/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - ..() - //Only mob/living types have stun handling - if(status && prob(throw_hit_chance) && iscarbon(hit_atom)) - baton_stun(hit_atom) - -/obj/item/melee/baton/loaded //this one starts with a cell pre-installed. - preload_cell_type = /obj/item/stock_parts/cell/high - -/obj/item/melee/baton/proc/deductcharge(chrgdeductamt, chargecheck = TRUE, explode = TRUE) - var/obj/item/stock_parts/cell/copper_top = get_cell() - if(!copper_top) - switch_status(FALSE, TRUE) - return FALSE - //Note this value returned is significant, as it will determine - //if a stun is applied or not - - copper_top.use(min(chrgdeductamt, copper_top.charge), explode) - if(QDELETED(src)) - return FALSE - if(status && (!copper_top || !copper_top.charge || (chargecheck && copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)))) - //we're below minimum, turn off - switch_status(FALSE) - -/obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE) - if(status != new_status) - status = new_status - if(!silent) - playsound(loc, "sparks", 75, 1, -1) - if(status) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/melee/baton/process() - deductcharge(hitcost * 0.004, FALSE, FALSE) - -/obj/item/melee/baton/update_icon() - if(status) - icon_state = "[initial(name)]_active" - else if(!cell) - icon_state = "[initial(name)]_nocell" - else - icon_state = "[initial(name)]" - -/obj/item/melee/baton/examine(mob/user) - . = ..() - var/obj/item/stock_parts/cell/copper_top = get_cell() - if(copper_top) - . += "\The [src] is [round(copper_top.percent())]% charged." - else - . += "\The [src] does not have a power source installed." - -/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell)) - var/obj/item/stock_parts/cell/C = W - if(cell) - to_chat(user, "[src] already has a cell.") - else - if(C.maxcharge < (hitcost * STUNBATON_CHARGE_LENIENCY)) - to_chat(user, "[src] requires a higher capacity cell.") - return - if(!user.transferItemToLoc(W, src)) - return - cell = W - to_chat(user, "You install a cell in [src].") - update_icon() - - else if(istype(W, /obj/item/screwdriver)) - if(cell) - cell.update_icon() - cell.forceMove(get_turf(src)) - cell = null - to_chat(user, "You remove the cell from [src].") - switch_status(FALSE, TRUE) - else - return ..() - -/obj/item/melee/baton/attack_self(mob/user) - var/obj/item/stock_parts/cell/copper_top = get_cell() - if(!copper_top || copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)) - switch_status(FALSE, TRUE) - if(!copper_top) - to_chat(user, "[src] does not have a power source!") - else - to_chat(user, "[src] is out of charge.") - else - switch_status(!status) - to_chat(user, "[src] is now [status ? "on" : "off"].") - add_fingerprint(user) - -/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) - if(status && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - clowning_around(user) - return - - if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)//CIT CHANGE - makes it impossible to baton in stamina softcrit - to_chat(user, "You're too exhausted for that.")//CIT CHANGE - ditto - return //CIT CHANGE - ditto - - if(iscyborg(M)) - ..() - return - - - if(ishuman(M)) - var/mob/living/carbon/human/L = M - if(check_martial_counter(L, user)) - return - - if(user.a_intent != INTENT_HARM) - if(status) - if(baton_stun(M, user)) - user.do_attack_animation(M) - user.adjustStaminaLossBuffered(getweight())//CIT CHANGE - makes stunbatonning others cost stamina - return - else - M.visible_message("[user] has prodded [M] with [src]. Luckily it was off.", \ - "[user] has prodded you with [src]. Luckily it was off") - else - if(status) - baton_stun(M, user) - ..() - - -/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user) - if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that - playsound(L, 'sound/weapons/genhit.ogg', 50, 1) - return FALSE - var/stunpwr = stunforce - var/obj/item/stock_parts/cell/our_cell = get_cell() - if(!our_cell) - switch_status(FALSE) - return FALSE - var/stuncharge = our_cell.charge - deductcharge(hitcost, FALSE) - if(QDELETED(src) || QDELETED(our_cell)) //it was rigged - return FALSE - if(stuncharge < hitcost) - if(stuncharge < (hitcost * STUNBATON_CHARGE_LENIENCY)) - L.visible_message("[user] has prodded [L] with [src]. Luckily it was out of charge.", \ - "[user] has prodded you with [src]. Luckily it was out of charge.") - return FALSE - stunpwr *= round(stuncharge/hitcost, 0.1) - - - L.Knockdown(stunpwr) - L.adjustStaminaLoss(stunpwr*0.1, affected_zone = (istype(user) ? user.zone_selected : BODY_ZONE_CHEST))//CIT CHANGE - makes stunbatons deal extra staminaloss. Todo: make this also deal pain when pain gets implemented. - L.apply_effect(EFFECT_STUTTER, stunforce) - SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) - if(user) - L.lastattacker = user.real_name - L.lastattackerckey = user.ckey - L.visible_message("[user] has stunned [L] with [src]!", \ - "[user] has stunned you with [src]!") - log_combat(user, L, "stunned") - - playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) - - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.forcesay(GLOB.hit_appends) - - - return TRUE - -/obj/item/melee/baton/proc/clowning_around(mob/living/user) - user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ - "You accidentally hit yourself with [src]!") - SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) - user.Knockdown(stunforce*3) - playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) - deductcharge(hitcost) - -/obj/item/melee/baton/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_SELF)) - switch_status(FALSE) - if(!iscyborg(loc)) - deductcharge(1000 / severity, TRUE, FALSE) - -//Makeshift stun baton. Replacement for stun gloves. -/obj/item/melee/baton/cattleprod - name = "stunprod" - desc = "An improvised stun baton." - icon_state = "stunprod_nocell" - item_state = "prod" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - force = 3 - throwforce = 5 - stunforce = 100 - hitcost = 2000 - throw_hit_chance = 10 - slot_flags = ITEM_SLOT_BACK - var/obj/item/assembly/igniter/sparkler - -/obj/item/melee/baton/cattleprod/Initialize() - . = ..() - sparkler = new (src) - sparkler.activate_cooldown = 5 - -/obj/item/melee/baton/cattleprod/baton_stun() - sparkler?.activate() - . = ..() - -#undef STUNBATON_CHARGE_LENIENCY +#define STUNBATON_CHARGE_LENIENCY 0.3 + +/obj/item/melee/baton + name = "stunbaton" + desc = "A stun baton for incapacitating people with." + icon_state = "stunbaton" + item_state = "baton" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + slot_flags = ITEM_SLOT_BELT + force = 10 + throwforce = 7 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("beaten") + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + + var/stunforce = 140 + var/status = FALSE + var/obj/item/stock_parts/cell/cell + var/hitcost = 1000 + var/throw_hit_chance = 35 + var/preload_cell_type //if not empty the baton starts with this type of cell + +/obj/item/melee/baton/get_cell() + . = cell + if(iscyborg(loc)) + var/mob/living/silicon/robot/R = loc + . = R.get_cell() + +/obj/item/melee/baton/suicide_act(mob/user) + user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +/obj/item/melee/baton/Initialize() + . = ..() + if(preload_cell_type) + if(!ispath(preload_cell_type,/obj/item/stock_parts/cell)) + log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") + else + cell = new preload_cell_type(src) + update_icon() + +/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + ..() + //Only mob/living types have stun handling + if(status && prob(throw_hit_chance) && iscarbon(hit_atom)) + baton_stun(hit_atom) + +/obj/item/melee/baton/loaded //this one starts with a cell pre-installed. + preload_cell_type = /obj/item/stock_parts/cell/high + +/obj/item/melee/baton/proc/deductcharge(chrgdeductamt, chargecheck = TRUE, explode = TRUE) + var/obj/item/stock_parts/cell/copper_top = get_cell() + if(!copper_top) + switch_status(FALSE, TRUE) + return FALSE + //Note this value returned is significant, as it will determine + //if a stun is applied or not + + copper_top.use(min(chrgdeductamt, copper_top.charge), explode) + if(QDELETED(src)) + return FALSE + if(status && (!copper_top || !copper_top.charge || (chargecheck && copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)))) + //we're below minimum, turn off + switch_status(FALSE) + +/obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE) + if(status != new_status) + status = new_status + if(!silent) + playsound(loc, "sparks", 75, 1, -1) + if(status) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/melee/baton/process() + deductcharge(hitcost * 0.004, FALSE, FALSE) + +/obj/item/melee/baton/update_icon() + if(status) + icon_state = "[initial(name)]_active" + else if(!cell) + icon_state = "[initial(name)]_nocell" + else + icon_state = "[initial(name)]" + +/obj/item/melee/baton/examine(mob/user) + . = ..() + var/obj/item/stock_parts/cell/copper_top = get_cell() + if(copper_top) + . += "\The [src] is [round(copper_top.percent())]% charged." + else + . += "\The [src] does not have a power source installed." + +/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell)) + var/obj/item/stock_parts/cell/C = W + if(cell) + to_chat(user, "[src] already has a cell.") + else + if(C.maxcharge < (hitcost * STUNBATON_CHARGE_LENIENCY)) + to_chat(user, "[src] requires a higher capacity cell.") + return + if(!user.transferItemToLoc(W, src)) + return + cell = W + to_chat(user, "You install a cell in [src].") + update_icon() + + else if(istype(W, /obj/item/screwdriver)) + if(cell) + cell.update_icon() + cell.forceMove(get_turf(src)) + cell = null + to_chat(user, "You remove the cell from [src].") + switch_status(FALSE, TRUE) + else + return ..() + +/obj/item/melee/baton/attack_self(mob/user) + var/obj/item/stock_parts/cell/copper_top = get_cell() + if(!copper_top || copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)) + switch_status(FALSE, TRUE) + if(!copper_top) + to_chat(user, "[src] does not have a power source!") + else + to_chat(user, "[src] is out of charge.") + else + switch_status(!status) + to_chat(user, "[src] is now [status ? "on" : "off"].") + add_fingerprint(user) + +/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) + if(status && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + clowning_around(user) + return + + if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)//CIT CHANGE - makes it impossible to baton in stamina softcrit + to_chat(user, "You're too exhausted for that.")//CIT CHANGE - ditto + return //CIT CHANGE - ditto + + if(iscyborg(M)) + ..() + return + + + if(ishuman(M)) + var/mob/living/carbon/human/L = M + if(check_martial_counter(L, user)) + return + + if(user.a_intent != INTENT_HARM) + if(status) + if(baton_stun(M, user)) + user.do_attack_animation(M) + user.adjustStaminaLossBuffered(getweight())//CIT CHANGE - makes stunbatonning others cost stamina + return + else + M.visible_message("[user] has prodded [M] with [src]. Luckily it was off.", \ + "[user] has prodded you with [src]. Luckily it was off") + else + if(status) + baton_stun(M, user) + ..() + + +/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user) + if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + playsound(L, 'sound/weapons/genhit.ogg', 50, 1) + return FALSE + var/stunpwr = stunforce + var/obj/item/stock_parts/cell/our_cell = get_cell() + if(!our_cell) + switch_status(FALSE) + return FALSE + var/stuncharge = our_cell.charge + deductcharge(hitcost, FALSE) + if(QDELETED(src) || QDELETED(our_cell)) //it was rigged + return FALSE + if(stuncharge < hitcost) + if(stuncharge < (hitcost * STUNBATON_CHARGE_LENIENCY)) + L.visible_message("[user] has prodded [L] with [src]. Luckily it was out of charge.", \ + "[user] has prodded you with [src]. Luckily it was out of charge.") + return FALSE + stunpwr *= round(stuncharge/hitcost, 0.1) + + + L.Knockdown(stunpwr) + L.adjustStaminaLoss(stunpwr*0.1, affected_zone = (istype(user) ? user.zone_selected : BODY_ZONE_CHEST))//CIT CHANGE - makes stunbatons deal extra staminaloss. Todo: make this also deal pain when pain gets implemented. + L.apply_effect(EFFECT_STUTTER, stunforce) + SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) + if(user) + L.lastattacker = user.real_name + L.lastattackerckey = user.ckey + L.visible_message("[user] has stunned [L] with [src]!", \ + "[user] has stunned you with [src]!") + log_combat(user, L, "stunned") + + playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) + + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.forcesay(GLOB.hit_appends) + + + return TRUE + +/obj/item/melee/baton/proc/clowning_around(mob/living/user) + user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ + "You accidentally hit yourself with [src]!") + SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) + user.Knockdown(stunforce*3) + playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) + deductcharge(hitcost) + +/obj/item/melee/baton/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_SELF)) + switch_status(FALSE) + if(!iscyborg(loc)) + deductcharge(1000 / severity, TRUE, FALSE) + +//Makeshift stun baton. Replacement for stun gloves. +/obj/item/melee/baton/cattleprod + name = "stunprod" + desc = "An improvised stun baton." + icon_state = "stunprod_nocell" + item_state = "prod" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + force = 3 + throwforce = 5 + stunforce = 100 + hitcost = 2000 + throw_hit_chance = 10 + slot_flags = ITEM_SLOT_BACK + var/obj/item/assembly/igniter/sparkler + +/obj/item/melee/baton/cattleprod/Initialize() + . = ..() + sparkler = new (src) + sparkler.activate_cooldown = 5 + +/obj/item/melee/baton/cattleprod/baton_stun() + sparkler?.activate() + . = ..() + +#undef STUNBATON_CHARGE_LENIENCY diff --git a/code/game/turfs/baseturf_skipover.dm b/code/game/turfs/baseturf_skipover.dm index 644714f8..4df8c86e 100644 --- a/code/game/turfs/baseturf_skipover.dm +++ b/code/game/turfs/baseturf_skipover.dm @@ -10,4 +10,9 @@ /turf/baseturf_skipover/shuttle name = "Shuttle baseturf skipover" - desc = "Acts as the bottom of the shuttle, if this isn't here the shuttle floor is broken through." \ No newline at end of file + desc = "Acts as the bottom of the shuttle, if this isn't here the shuttle floor is broken through." + +/turf/baseturf_bottom + name = "Z-level baseturf placeholder" + desc = "Marker for z-level baseturf, usually resolves to space." + baseturfs = /turf/baseturf_bottom diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index 6a055bbd..84b6cbb3 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -1,6 +1,7 @@ // This is a list of turf types we dont want to assign to baseturfs unless through initialization or explicitly GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( /turf/open/space, + /turf/baseturf_bottom ))) /turf/proc/empty(turf_type=/turf/open/space, baseturf_type, list/ignore_typecache, flags) @@ -56,12 +57,20 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( // Creates a new turf // new_baseturfs can be either a single type or list of types, formated the same as baseturfs. see turf.dm /turf/proc/ChangeTurf(path, list/new_baseturfs, flags) - if(!path) - return - if(path == /turf/open/space/basic) - // basic doesn't initialize and this will cause issues - // no warning though because this can happen naturaly as a result of it being built on top of - path = /turf/open/space + switch(path) + if(null) + return + if(/turf/baseturf_bottom) + path = SSmapping.level_trait(z, ZTRAIT_BASETURF) || /turf/open/space + if (!ispath(path)) + path = text2path(path) + if (!ispath(path)) + warning("Z-level [z] has invalid baseturf '[SSmapping.level_trait(z, ZTRAIT_BASETURF)]'") + path = /turf/open/space + if(/turf/open/space/basic) + // basic doesn't initialize and this will cause issues + // no warning though because this can happen naturaly as a result of it being built on top of + path = /turf/open/space if(!GLOB.use_preloader && path == type && !(flags & CHANGETURF_FORCEOP)) // Don't no-op if the map loader requires it to be reconstructed return src if(flags & CHANGETURF_SKIP) @@ -215,7 +224,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( newT.assemble_baseturfs(initial(fake_turf_type.baseturfs)) // The baseturfs list is created like roundstart if(!length(newT.baseturfs)) newT.baseturfs = list(baseturfs) - newT.baseturfs -= newT.baseturfs & GLOB.blacklisted_automated_baseturfs + newT.baseturfs -= GLOB.blacklisted_automated_baseturfs newT.baseturfs.Insert(1, old_baseturfs) // The old baseturfs are put underneath return newT if(!length(baseturfs)) diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm index 47db413e..ad18316c 100644 --- a/code/game/turfs/simulated/floor/plating.dm +++ b/code/game/turfs/simulated/floor/plating.dm @@ -11,7 +11,7 @@ name = "plating" icon_state = "plating" intact = FALSE - baseturfs = /turf/open/space + baseturfs = /turf/baseturf_bottom footstep = FOOTSTEP_PLATING barefootstep = FOOTSTEP_HARD_BAREFOOT clawfootstep = FOOTSTEP_HARD_CLAW diff --git a/code/game/turfs/simulated/floor/plating/asteroid.dm b/code/game/turfs/simulated/floor/plating/asteroid.dm index c66905cd..2533973c 100644 --- a/code/game/turfs/simulated/floor/plating/asteroid.dm +++ b/code/game/turfs/simulated/floor/plating/asteroid.dm @@ -76,11 +76,6 @@ for(var/obj/item/stack/ore/O in src) SEND_SIGNAL(W, COMSIG_PARENT_ATTACKBY, O) -/turf/open/floor/plating/asteroid/singularity_act() - if(is_planet_level(z)) - return ..() - ScrapeAway() - /turf/open/floor/plating/asteroid/ex_act(severity, target) . = SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) contents_explosion(severity, target) @@ -131,7 +126,8 @@ /turf/open/floor/plating/asteroid/airless - initial_gas_mix = "TEMP=2.7" + initial_gas_mix = AIRLESS_ATMOS + baseturfs = /turf/open/floor/plating/asteroid/airless turf_type = /turf/open/floor/plating/asteroid/airless diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 5652e067..d1394e7c 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -9,7 +9,7 @@ // A list will be created in initialization that figures out the baseturf's baseturf etc. // In the case of a list it is sorted from bottom layer to top. // This shouldn't be modified directly, use the helper procs. - var/list/baseturfs = /turf/open/space + var/list/baseturfs = /turf/baseturf_bottom var/temperature = T20C var/to_be_destroyed = 0 //Used for fire, if a melting temperature was reached, it will be destroyed diff --git a/code/game/world.dm b/code/game/world.dm index 64ebce37..9ed3940b 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,332 +1,333 @@ -#define RESTART_COUNTER_PATH "data/round_counter.txt" -#define TOPIC_LENGTH_LIMIT 8192 //The reason why this is so large is to allow TGS topic calls to function without issue. -#define TOPIC_COOLDOWN 1 //90% sure there isnt anything that does multiple topic calls in a single decisecond. - -GLOBAL_VAR(restart_counter) - -//This happens after the Master subsystem new(s) (it's a global datum) -//So subsystems globals exist, but are not initialised -/world/New() - var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so") - if (fexists(extools)) - call(extools, "maptick_initialize")() - enable_debugger() - - log_world("World loaded at [TIME_STAMP("hh:mm:ss", FALSE)]!") - - SetupExternalRSC() - - GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl - - make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) - - TgsNew() - - GLOB.revdata = new - - config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - - //SetupLogs depends on the RoundID, so lets check - //DB schema and set RoundID if we can - SSdbcore.CheckSchemaVersion() - SSdbcore.SetRoundID() - SetupLogs() - -#ifndef USE_CUSTOM_ERROR_HANDLER - world.log = file("[GLOB.log_directory]/dd.log") -#endif - - load_admins() - load_patreons() - LoadVerbs(/datum/verbs/menu) - if(CONFIG_GET(flag/usewhitelist)) - load_whitelist() - LoadBans() - reload_custom_roundstart_items_list()//Cit change - loads donator items. Remind me to remove when I port over bay's loadout system - - GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 - - if(fexists(RESTART_COUNTER_PATH)) - GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) - fdel(RESTART_COUNTER_PATH) - - if(NO_INIT_PARAMETER in params) - return - - cit_initialize() - - Master.Initialize(10, FALSE, TRUE) - - if(TEST_RUN_PARAMETER in params) - HandleTestRun() - -/world/proc/HandleTestRun() - //trigger things to run the whole process - Master.sleep_offline_after_initializations = FALSE - SSticker.start_immediately = TRUE - CONFIG_SET(number/round_end_countdown, 0) - var/datum/callback/cb -#ifdef UNIT_TESTS - cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) -#else - cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) -#endif - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) - -/world/proc/SetupExternalRSC() -#if (PRELOAD_RSC == 0) - GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") - var/i=1 - while(i<=GLOB.external_rsc_urls.len) - if(GLOB.external_rsc_urls[i]) - i++ - else - GLOB.external_rsc_urls.Cut(i,i+1) -#endif - -/world/proc/SetupLogs() - var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] - if(!override_dir) - var/realtime = world.realtime - var/texttime = time2text(realtime, "YYYY/MM/DD") - GLOB.log_directory = "data/logs/[texttime]/round-" - GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" - GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" - if(GLOB.round_id) - GLOB.log_directory += "[GLOB.round_id]" - GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" - GLOB.picture_log_directory += "[GLOB.round_id]" - else - var/timestamp = replacetext(TIME_STAMP("hh:mm:ss", FALSE), ":", ".") - GLOB.log_directory += "[timestamp]" - GLOB.picture_log_directory += "[timestamp]" - GLOB.picture_logging_prefix += "T_[timestamp]_" - else - GLOB.log_directory = "data/logs/[override_dir]" - GLOB.picture_logging_prefix = "O_[override_dir]_" - GLOB.picture_log_directory = "data/picture_logs/[override_dir]" - - GLOB.world_game_log = "[GLOB.log_directory]/game.log" - GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" - GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" - GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" - GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" - GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" - GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" - GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" - GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" - GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" - GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" - GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" - -#ifdef UNIT_TESTS - GLOB.test_log = file("[GLOB.log_directory]/tests.log") - start_log(GLOB.test_log) -#endif - start_log(GLOB.world_game_log) - start_log(GLOB.world_attack_log) - start_log(GLOB.world_pda_log) - start_log(GLOB.world_telecomms_log) - start_log(GLOB.world_manifest_log) - start_log(GLOB.world_href_log) - start_log(GLOB.world_qdel_log) - start_log(GLOB.world_runtime_log) - start_log(GLOB.world_job_debug_log) - - GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently - if(fexists(GLOB.config_error_log)) - fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") - fdel(GLOB.config_error_log) - - if(GLOB.round_id) - log_game("Round ID: [GLOB.round_id]") - - // This was printed early in startup to the world log and config_error.log, - // but those are both private, so let's put the commit info in the runtime - // log which is ultimately public. - log_runtime(GLOB.revdata.get_log_message()) - -/world/Topic(T, addr, master, key) - var/static/list/bannedsourceaddrs = list() - var/static/list/lasttimeaddr = list() - - if(addr in bannedsourceaddrs) - return - - if(length(T) >= TOPIC_LENGTH_LIMIT) - log_topic("Oversized topic, banning address. from:[addr]") - bannedsourceaddrs |= addr - return - - if(addr in lasttimeaddr && world.time < (lasttimeaddr["[addr]"] + TOPIC_COOLDOWN)) - log_topic("Too many topic calls from address in [TOPIC_COOLDOWN] ds, banning address. from:[addr]") - bannedsourceaddrs |= addr - return - - lasttimeaddr["[addr]"] = world.time - - - TGS_TOPIC //redirect to server tools if necessary - - var/static/list/topic_handlers = TopicHandlers() - - var/list/input = params2list(T) - var/datum/world_topic/handler - for(var/I in topic_handlers) - if(I in input) - handler = topic_handlers[I] - break - - if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) - log_topic("\"[T]\", from:[addr], master:[master], key:[key]") - - if(!handler) - return - - handler = new handler() - return handler.TryRun(input) - -/world/proc/AnnouncePR(announcement, list/payload) - var/static/list/PRcounts = list() //PR id -> number of times announced this round - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [announcement]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/world/proc/FinishTestRun() - set waitfor = FALSE - var/list/fail_reasons - if(GLOB) - if(GLOB.total_runtimes != 0) - fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") -#ifdef UNIT_TESTS - if(GLOB.failed_any_test) - LAZYADD(fail_reasons, "Unit Tests failed!") -#endif - if(!GLOB.log_directory) - LAZYADD(fail_reasons, "Missing GLOB.log_directory!") - else - fail_reasons = list("Missing GLOB!") - if(!fail_reasons) - text2file("Success!", "[GLOB.log_directory]/clean_run.lk") - else - log_world("Test run failed!\n[fail_reasons.Join("\n")]") - sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes - qdel(src) //shut it down - -/world/Reboot(reason = 0, fast_track = FALSE) - TgsReboot() - if (reason || fast_track) //special reboot, do none of the normal stuff - if (usr) - log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") - message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") - to_chat(world, "Rebooting World immediately due to host request") - else - to_chat(world, "Rebooting world...") - Master.Shutdown() //run SS shutdowns - - if(TEST_RUN_PARAMETER in params) - FinishTestRun() - return - - if(TgsAvailable()) - var/do_hard_reboot - // check the hard reboot counter - var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) - switch(ruhr) - if(-1) - do_hard_reboot = FALSE - if(0) - do_hard_reboot = TRUE - else - if(GLOB.restart_counter >= ruhr) - do_hard_reboot = TRUE - else - text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) - do_hard_reboot = FALSE - - if(do_hard_reboot) - log_world("World hard rebooted at [TIME_STAMP("hh:mm:ss", FALSE)]") - shutdown_logging() // See comment below. - TgsEndProcess() - - log_world("World rebooted at [TIME_STAMP("hh:mm:ss", FALSE)]") - shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. - ..() - -/world/proc/update_status() - - var/list/features = list() - - /*if(GLOB.master_mode) CIT CHANGE - hides the gamemode from the hub entry, removes some useless info from the hub entry - features += GLOB.master_mode - - if (!GLOB.enter_allowed) - features += "closed"*/ - - var/s = "" - var/hostedby - if(config) - var/server_name = CONFIG_GET(string/servername) - if (server_name) - s += "[server_name] — " - /*features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" CIT CHANGE - removes some useless info from the hub entry - if(CONFIG_GET(flag/allow_vote_mode)) - features += "vote" - if(CONFIG_GET(flag/allow_ai)) - features += "AI allowed"*/ - hostedby = CONFIG_GET(string/hostedby) - - s += "[station_name()]"; - s += " (" - s += "" //Change this to wherever you want the hub to link to. CIT CHANGE - links to cit's website on the hub - s += "Website" //Replace this with something else. Or ever better, delete it and uncomment the game version. CIT CHANGE - modifies the hub entry link - s += "" - s += ")\]" //CIT CHANGE - encloses the server title in brackets to make the hub entry fancier - s += "
    [CONFIG_GET(string/servertagline)]
    " //CIT CHANGE - adds a tagline! - - var/n = 0 - for (var/mob/M in GLOB.player_list) - if (M.client) - n++ - - if(SSmapping.config) // this just stops the runtime, honk. - features += "[SSmapping.config.map_name]" //CIT CHANGE - makes the hub entry display the current map - - if(get_security_level())//CIT CHANGE - makes the hub entry show the security level - features += "[get_security_level()] alert" - - if (n > 1) - features += "~[n] players" - else if (n > 0) - features += "~[n] player" - - if (!host && hostedby) - features += "hosted by [hostedby]" - - if (features) - s += "\[[jointext(features, ", ")]" //CIT CHANGE - replaces the colon here with a left bracket - - status = s - -/world/proc/update_hub_visibility(new_visibility) - if(new_visibility == GLOB.hub_visibility) - return - GLOB.hub_visibility = new_visibility - if(GLOB.hub_visibility) - hub_password = "kMZy3U5jJHSiBQjr" - else - hub_password = "SORRYNOPASSWORD" - -/world/proc/incrementMaxZ() - maxz++ - SSmobs.MaxZChanged() - SSidlenpcpool.MaxZChanged() - +#define RESTART_COUNTER_PATH "data/round_counter.txt" +#define TOPIC_LENGTH_LIMIT 8192 //The reason why this is so large is to allow TGS topic calls to function without issue. +#define TOPIC_COOLDOWN 1 //90% sure there isnt anything that does multiple topic calls in a single decisecond. + +GLOBAL_VAR(restart_counter) + +//This happens after the Master subsystem new(s) (it's a global datum) +//So subsystems globals exist, but are not initialised +/world/New() + var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so") + if (fexists(extools)) + call(extools, "maptick_initialize")() + enable_debugger() + + log_world("World loaded at [TIME_STAMP("hh:mm:ss", FALSE)]!") + + SetupExternalRSC() + + GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl + + make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) + + TgsNew() + + GLOB.revdata = new + + config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + + //SetupLogs depends on the RoundID, so lets check + //DB schema and set RoundID if we can + SSdbcore.CheckSchemaVersion() + SSdbcore.SetRoundID() + SetupLogs() + +#ifndef USE_CUSTOM_ERROR_HANDLER + world.log = file("[GLOB.log_directory]/dd.log") +#endif + + load_admins() + load_patreons() + LoadVerbs(/datum/verbs/menu) + if(CONFIG_GET(flag/usewhitelist)) + load_whitelist() + LoadBans() + reload_custom_roundstart_items_list()//Cit change - loads donator items. Remind me to remove when I port over bay's loadout system + + GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 + + if(fexists(RESTART_COUNTER_PATH)) + GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) + fdel(RESTART_COUNTER_PATH) + + if(NO_INIT_PARAMETER in params) + return + + cit_initialize() + + Master.Initialize(10, FALSE, TRUE) + + if(TEST_RUN_PARAMETER in params) + HandleTestRun() + +/world/proc/HandleTestRun() + //trigger things to run the whole process + Master.sleep_offline_after_initializations = FALSE + SSticker.start_immediately = TRUE + CONFIG_SET(number/round_end_countdown, 0) + var/datum/callback/cb +#ifdef UNIT_TESTS + cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) +#else + cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) +#endif + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) + +/world/proc/SetupExternalRSC() +#if (PRELOAD_RSC == 0) + GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") + var/i=1 + while(i<=GLOB.external_rsc_urls.len) + if(GLOB.external_rsc_urls[i]) + i++ + else + GLOB.external_rsc_urls.Cut(i,i+1) +#endif + +/world/proc/SetupLogs() + var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] + if(!override_dir) + var/realtime = world.realtime + var/texttime = time2text(realtime, "YYYY/MM/DD") + GLOB.log_directory = "data/logs/[texttime]/round-" + GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" + GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" + if(GLOB.round_id) + GLOB.log_directory += "[GLOB.round_id]" + GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" + GLOB.picture_log_directory += "[GLOB.round_id]" + else + var/timestamp = replacetext(TIME_STAMP("hh:mm:ss", FALSE), ":", ".") + GLOB.log_directory += "[timestamp]" + GLOB.picture_log_directory += "[timestamp]" + GLOB.picture_logging_prefix += "T_[timestamp]_" + else + GLOB.log_directory = "data/logs/[override_dir]" + GLOB.picture_logging_prefix = "O_[override_dir]_" + GLOB.picture_log_directory = "data/picture_logs/[override_dir]" + + GLOB.world_game_log = "[GLOB.log_directory]/game.log" + GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" + GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" + GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" + GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" + GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" + GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" + GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" + GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" + GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" + GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" + GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" + GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" + +#ifdef UNIT_TESTS + GLOB.test_log = file("[GLOB.log_directory]/tests.log") + start_log(GLOB.test_log) +#endif + start_log(GLOB.world_game_log) + start_log(GLOB.world_attack_log) + start_log(GLOB.world_pda_log) + start_log(GLOB.world_telecomms_log) + start_log(GLOB.world_manifest_log) + start_log(GLOB.world_href_log) + start_log(GLOB.world_qdel_log) + start_log(GLOB.world_runtime_log) + start_log(GLOB.world_job_debug_log) + + GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently + if(fexists(GLOB.config_error_log)) + fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") + fdel(GLOB.config_error_log) + + if(GLOB.round_id) + log_game("Round ID: [GLOB.round_id]") + + // This was printed early in startup to the world log and config_error.log, + // but those are both private, so let's put the commit info in the runtime + // log which is ultimately public. + log_runtime(GLOB.revdata.get_log_message()) + +/world/Topic(T, addr, master, key) + var/static/list/bannedsourceaddrs = list() + var/static/list/lasttimeaddr = list() + + if(addr in bannedsourceaddrs) + return + + if(length(T) >= TOPIC_LENGTH_LIMIT) + log_topic("Oversized topic, banning address. from:[addr]") + bannedsourceaddrs |= addr + return + + if(addr in lasttimeaddr && world.time < (lasttimeaddr["[addr]"] + TOPIC_COOLDOWN)) + log_topic("Too many topic calls from address in [TOPIC_COOLDOWN] ds, banning address. from:[addr]") + bannedsourceaddrs |= addr + return + + lasttimeaddr["[addr]"] = world.time + + + TGS_TOPIC //redirect to server tools if necessary + + var/static/list/topic_handlers = TopicHandlers() + + var/list/input = params2list(T) + var/datum/world_topic/handler + for(var/I in topic_handlers) + if(I in input) + handler = topic_handlers[I] + break + + if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) + log_topic("\"[T]\", from:[addr], master:[master], key:[key]") + + if(!handler) + return + + handler = new handler() + return handler.TryRun(input) + +/world/proc/AnnouncePR(announcement, list/payload) + var/static/list/PRcounts = list() //PR id -> number of times announced this round + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [announcement]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/world/proc/FinishTestRun() + set waitfor = FALSE + var/list/fail_reasons + if(GLOB) + if(GLOB.total_runtimes != 0) + fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") +#ifdef UNIT_TESTS + if(GLOB.failed_any_test) + LAZYADD(fail_reasons, "Unit Tests failed!") +#endif + if(!GLOB.log_directory) + LAZYADD(fail_reasons, "Missing GLOB.log_directory!") + else + fail_reasons = list("Missing GLOB!") + if(!fail_reasons) + text2file("Success!", "[GLOB.log_directory]/clean_run.lk") + else + log_world("Test run failed!\n[fail_reasons.Join("\n")]") + sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes + qdel(src) //shut it down + +/world/Reboot(reason = 0, fast_track = FALSE) + TgsReboot() + if (reason || fast_track) //special reboot, do none of the normal stuff + if (usr) + log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") + message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") + to_chat(world, "Rebooting World immediately due to host request") + else + to_chat(world, "Rebooting world...") + Master.Shutdown() //run SS shutdowns + + if(TEST_RUN_PARAMETER in params) + FinishTestRun() + return + + if(TgsAvailable()) + var/do_hard_reboot + // check the hard reboot counter + var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) + switch(ruhr) + if(-1) + do_hard_reboot = FALSE + if(0) + do_hard_reboot = TRUE + else + if(GLOB.restart_counter >= ruhr) + do_hard_reboot = TRUE + else + text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) + do_hard_reboot = FALSE + + if(do_hard_reboot) + log_world("World hard rebooted at [TIME_STAMP("hh:mm:ss", FALSE)]") + shutdown_logging() // See comment below. + TgsEndProcess() + + log_world("World rebooted at [TIME_STAMP("hh:mm:ss", FALSE)]") + shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. + ..() + +/world/proc/update_status() + + var/list/features = list() + + /*if(GLOB.master_mode) CIT CHANGE - hides the gamemode from the hub entry, removes some useless info from the hub entry + features += GLOB.master_mode + + if (!GLOB.enter_allowed) + features += "closed"*/ + + var/s = "" + var/hostedby + if(config) + var/server_name = CONFIG_GET(string/servername) + if (server_name) + s += "[server_name] — " + /*features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" CIT CHANGE - removes some useless info from the hub entry + if(CONFIG_GET(flag/allow_vote_mode)) + features += "vote" + if(CONFIG_GET(flag/allow_ai)) + features += "AI allowed"*/ + hostedby = CONFIG_GET(string/hostedby) + + s += "[station_name()]"; + s += " (" + s += "" //Change this to wherever you want the hub to link to. CIT CHANGE - links to cit's website on the hub + s += "Website" //Replace this with something else. Or ever better, delete it and uncomment the game version. CIT CHANGE - modifies the hub entry link + s += "" + s += ")\]" //CIT CHANGE - encloses the server title in brackets to make the hub entry fancier + s += "
    [CONFIG_GET(string/servertagline)]
    " //CIT CHANGE - adds a tagline! + + var/n = 0 + for (var/mob/M in GLOB.player_list) + if (M.client) + n++ + + if(SSmapping.config) // this just stops the runtime, honk. + features += "[SSmapping.config.map_name]" //CIT CHANGE - makes the hub entry display the current map + + if(get_security_level())//CIT CHANGE - makes the hub entry show the security level + features += "[get_security_level()] alert" + + if (n > 1) + features += "~[n] players" + else if (n > 0) + features += "~[n] player" + + if (!host && hostedby) + features += "hosted by [hostedby]" + + if (features) + s += "\[[jointext(features, ", ")]" //CIT CHANGE - replaces the colon here with a left bracket + + status = s + +/world/proc/update_hub_visibility(new_visibility) + if(new_visibility == GLOB.hub_visibility) + return + GLOB.hub_visibility = new_visibility + if(GLOB.hub_visibility) + hub_password = "kMZy3U5jJHSiBQjr" + else + hub_password = "SORRYNOPASSWORD" + +/world/proc/incrementMaxZ() + maxz++ + SSmobs.MaxZChanged() + SSidlenpcpool.MaxZChanged() + diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 0fe8859c..3a0b1e97 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -1,358 +1,377 @@ -//- 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. - -GLOBAL_PROTECT(admin_verbs_debug_mapping) -GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( - /client/proc/camera_view, //-errorage - /client/proc/sec_camera_report, //-errorage - /client/proc/intercom_view, //-errorage - /client/proc/air_status, //Air things - /client/proc/Cell, //More air things - /client/proc/atmosscan, //check plumbing - /client/proc/powerdebug, //check power - /client/proc/count_objects_on_z_level, - /client/proc/count_objects_all, - /client/proc/cmd_assume_direct_control, //-errorage - /client/proc/startSinglo, - /client/proc/set_server_fps, //allows you to set the ticklag. - /client/proc/cmd_admin_grantfullaccess, - /client/proc/cmd_admin_areatest_all, - /client/proc/cmd_admin_areatest_station, - /client/proc/cmd_admin_test_atmos_controllers, - /client/proc/cmd_admin_rejuvenate, - /datum/admins/proc/show_traitor_panel, - /client/proc/disable_communication, - /client/proc/cmd_show_at_list, - /client/proc/cmd_show_at_markers, - /client/proc/manipulate_organs, - /client/proc/start_line_profiling, - /client/proc/stop_line_profiling, - /client/proc/show_line_profiling, - /client/proc/create_mapping_job_icons, - /client/proc/debug_z_levels, - /client/proc/place_ruin -)) - -/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" - - var/on = FALSE - for(var/turf/T in world) - if(T.maptext) - on = TRUE - T.maptext = null - - if(!on) - var/list/seen = list() - for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) - for(var/turf/T in C.can_see()) - seen[T]++ - for(var/turf/T in seen) - T.maptext = "[seen[T]]" - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") - - - -/client/proc/sec_camera_report() - set category = "Mapping" - set name = "Camera Report" - - if(!Master) - alert(usr,"Master_controller not found.","Sec Camera Report") - return 0 - - var/list/obj/machinery/camera/CL = list() - - for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) - CL += C - - var/output = {"Camera Abnormalities Report
    -The following abnormalities 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 cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - 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 cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
    • " - if(C1.loc == C2.loc) - output += "
    • Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
    • " - var/turf/T = get_step(C1,turn(C1.dir,180)) - if(!T || !isturf(T) || !T.density ) - if(!(locate(/obj/structure/grille) in T)) - var/window_check = 0 - for(var/obj/structure/window/W in T) - if (W.dir == turn(C1.dir,180) || W.dir in list(5,6,9,10) ) - window_check = 1 - break - if(!window_check) - output += "
    • Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]
    • " - - output += "
    " - usr << browse(output,"window=airreport;size=1000x500") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //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" - - var/static/intercom_range_display_status = FALSE - intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something - - 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) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_show_at_list() - set category = "Mapping" - set name = "Show roundstart AT list" - set desc = "Displays a list of active turfs coordinates at roundstart" - - var/dat = {"Coordinate list of Active Turfs at Roundstart -
    Real-time Active Turfs list you can see in Air Subsystem at active_turfs var
    "} - - for(var/t in GLOB.active_turfs_startlist) - var/turf/T = t - dat += "[ADMIN_VERBOSEJMP(T)]\n" - dat += "
    " - - usr << browse(dat, "window=at_list") - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_show_at_markers() - set category = "Mapping" - set name = "Show roundstart AT markers" - set desc = "Places a marker on all active-at-roundstart turfs" - - var/count = 0 - for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers) - qdel(AT) - count++ - - if(count) - to_chat(usr, "[count] AT markers removed.") - else - for(var/t in GLOB.active_turfs_startlist) - new /obj/effect/abstract/marker/at(t) - count++ - to_chat(usr, "[count] AT markers placed.") - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers") - -/client/proc/enable_debug_verbs() - set category = "Debug" - set name = "Debug verbs - Enable" - if(!check_rights(R_DEBUG)) - return - verbs -= /client/proc/enable_debug_verbs - verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/disable_debug_verbs() - set category = "Debug" - set name = "Debug verbs - Disable" - verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) - verbs += /client/proc/enable_debug_verbs - SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //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" - var/level = input("Which z-level?","Level?") as text - if(!level) - return - var/num_level = text2num(level) - if(!num_level) - return - if(!isnum(num_level)) - return - - var/type_text = input("Which type path?","Path?") as text - 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]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //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" - - var/type_text = input("Which type path?","") as text - 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") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -//This proc is intended to detect lag problems relating to communication procs -GLOBAL_VAR_INIT(say_disabled, FALSE) -/client/proc/disable_communication() - set category = "Mapping" - set name = "Disable all communication verbs" - - GLOB.say_disabled = !GLOB.say_disabled - if(GLOB.say_disabled) - message_admins("[key] used 'Disable all communication verbs', killing all communication methods.") - else - message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.") - -//This generates the icon states for job starting location landmarks. -/client/proc/create_mapping_job_icons() - set name = "Generate job landmarks icons" - set category = "Mapping" - var/icon/final = icon() - var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted - D.setDir(SOUTH) - for(var/job in subtypesof(/datum/job)) - var/datum/job/JB = new job - switch(JB.title) - if("AI") - final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI") - if("Cyborg") - final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg") - else - for(var/obj/item/I in D) - qdel(I) - randomize_human(D) - JB.equip(D, TRUE, FALSE) - COMPILE_OVERLAYS(D) - var/icon/I = icon(getFlatIcon(D), frame = 1) - final.Insert(I, JB.title) - qdel(D) - //Also add the x - for(var/x_number in 1 to 4) - final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]") - fcopy(final, "icons/mob/landmarks.dmi") - -/client/proc/debug_z_levels() - set name = "Debug Z-Levels" - set category = "Mapping" - - var/list/z_list = SSmapping.z_list - var/list/messages = list() - messages += "World: [world.maxx] x [world.maxy] x [world.maxz]
    " - - var/list/linked_levels = list() - var/min_x = INFINITY - var/min_y = INFINITY - var/max_x = -INFINITY - var/max_y = -INFINITY - - for(var/z in 1 to max(world.maxz, z_list.len)) - if (z > z_list.len) - messages += "[z]: Unmanaged (out of bounds)
    " - continue - var/datum/space_level/S = z_list[z] - if (!S) - messages += "[z]: Unmanaged (null)
    " - continue - var/linkage - switch (S.linkage) - if (UNAFFECTED) - linkage = "no linkage" - if (SELFLOOPING) - linkage = "self-looping" - if (CROSSLINKED) - linkage = "linked at ([S.xi], [S.yi])" - linked_levels += S - min_x = min(min_x, S.xi) - min_y = min(min_y, S.yi) - max_x = max(max_x, S.xi) - max_y = max(max_y, S.yi) - else - linkage = "unknown linkage '[S.linkage]'" - - messages += "[z]: [S.name], [linkage], traits: [json_encode(S.traits)]
    " - if (S.z_value != z) - messages += "-- z_value is [S.z_value], should be [z]
    " - if (S.name == initial(S.name)) - messages += "-- name not set
    " - if (z > world.maxz) - messages += "-- exceeds max z" - - var/grid[max_x - min_x + 1][max_y - min_y + 1] - for(var/datum/space_level/S in linked_levels) - grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value - - messages += "" - for(var/y in max_y to min_y step -1) - var/list/part = list() - for(var/x in min_x to max_x) - part += "[grid[x - min_x + 1][y - min_y + 1]]" - messages += "" - messages += "
    [part.Join("")]
    " - - to_chat(src, messages.Join("")) +//- 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. + +GLOBAL_PROTECT(admin_verbs_debug_mapping) +GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( + /client/proc/camera_view, //-errorage + /client/proc/sec_camera_report, //-errorage + /client/proc/intercom_view, //-errorage + /client/proc/air_status, //Air things + /client/proc/Cell, //More air things + /client/proc/atmosscan, //check plumbing + /client/proc/powerdebug, //check power + /client/proc/count_objects_on_z_level, + /client/proc/count_objects_all, + /client/proc/cmd_assume_direct_control, //-errorage + /client/proc/startSinglo, + /client/proc/set_server_fps, //allows you to set the ticklag. + /client/proc/cmd_admin_grantfullaccess, + /client/proc/cmd_admin_areatest_all, + /client/proc/cmd_admin_areatest_station, + #ifdef TESTING + /client/proc/see_dirty_varedits, + #endif + /client/proc/cmd_admin_test_atmos_controllers, + /client/proc/cmd_admin_rejuvenate, + /datum/admins/proc/show_traitor_panel, + /client/proc/disable_communication, + /client/proc/cmd_show_at_list, + /client/proc/cmd_show_at_markers, + /client/proc/manipulate_organs, + /client/proc/start_line_profiling, + /client/proc/stop_line_profiling, + /client/proc/show_line_profiling, + /client/proc/create_mapping_job_icons, + /client/proc/debug_z_levels, + /client/proc/place_ruin +)) + +/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" + + var/on = FALSE + for(var/turf/T in world) + if(T.maptext) + on = TRUE + T.maptext = null + + if(!on) + var/list/seen = list() + for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) + for(var/turf/T in C.can_see()) + seen[T]++ + for(var/turf/T in seen) + T.maptext = "[seen[T]]" + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") + +#ifdef TESTING +GLOBAL_LIST_EMPTY(dirty_vars) + + +/client/proc/see_dirty_varedits() + set category = "Mapping" + set name = "Dirty Varedits" + + var/list/dat = list() + dat += "

    Abandon all hope ye who enter here



    " + for(var/thing in GLOB.dirty_vars) + dat += "[thing]
    " + CHECK_TICK + var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750) + popup.set_content(dat.Join()) + popup.open() +#endif + +/client/proc/sec_camera_report() + set category = "Mapping" + set name = "Camera Report" + + if(!Master) + alert(usr,"Master_controller not found.","Sec Camera Report") + return 0 + + var/list/obj/machinery/camera/CL = list() + + for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) + CL += C + + var/output = {"Camera Abnormalities Report
    +The following abnormalities 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 cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - 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 cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
    • " + if(C1.loc == C2.loc) + output += "
    • Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
    • " + var/turf/T = get_step(C1,turn(C1.dir,180)) + if(!T || !isturf(T) || !T.density ) + if(!(locate(/obj/structure/grille) in T)) + var/window_check = 0 + for(var/obj/structure/window/W in T) + if (W.dir == turn(C1.dir,180) || W.dir in list(5,6,9,10) ) + window_check = 1 + break + if(!window_check) + output += "
    • Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]
    • " + + output += "
    " + usr << browse(output,"window=airreport;size=1000x500") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //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" + + var/static/intercom_range_display_status = FALSE + intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something + + 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) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_show_at_list() + set category = "Mapping" + set name = "Show roundstart AT list" + set desc = "Displays a list of active turfs coordinates at roundstart" + + var/dat = {"Coordinate list of Active Turfs at Roundstart +
    Real-time Active Turfs list you can see in Air Subsystem at active_turfs var
    "} + + for(var/t in GLOB.active_turfs_startlist) + var/turf/T = t + dat += "[ADMIN_VERBOSEJMP(T)]\n" + dat += "
    " + + usr << browse(dat, "window=at_list") + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_show_at_markers() + set category = "Mapping" + set name = "Show roundstart AT markers" + set desc = "Places a marker on all active-at-roundstart turfs" + + var/count = 0 + for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers) + qdel(AT) + count++ + + if(count) + to_chat(usr, "[count] AT markers removed.") + else + for(var/t in GLOB.active_turfs_startlist) + new /obj/effect/abstract/marker/at(t) + count++ + to_chat(usr, "[count] AT markers placed.") + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers") + +/client/proc/enable_debug_verbs() + set category = "Debug" + set name = "Debug verbs - Enable" + if(!check_rights(R_DEBUG)) + return + verbs -= /client/proc/enable_debug_verbs + verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/disable_debug_verbs() + set category = "Debug" + set name = "Debug verbs - Disable" + verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) + verbs += /client/proc/enable_debug_verbs + SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //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" + var/level = input("Which z-level?","Level?") as text + if(!level) + return + var/num_level = text2num(level) + if(!num_level) + return + if(!isnum(num_level)) + return + + var/type_text = input("Which type path?","Path?") as text + 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]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //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" + + var/type_text = input("Which type path?","") as text + 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") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +//This proc is intended to detect lag problems relating to communication procs +GLOBAL_VAR_INIT(say_disabled, FALSE) +/client/proc/disable_communication() + set category = "Mapping" + set name = "Disable all communication verbs" + + GLOB.say_disabled = !GLOB.say_disabled + if(GLOB.say_disabled) + message_admins("[key] used 'Disable all communication verbs', killing all communication methods.") + else + message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.") + +//This generates the icon states for job starting location landmarks. +/client/proc/create_mapping_job_icons() + set name = "Generate job landmarks icons" + set category = "Mapping" + var/icon/final = icon() + var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted + D.setDir(SOUTH) + for(var/job in subtypesof(/datum/job)) + var/datum/job/JB = new job + switch(JB.title) + if("AI") + final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI") + if("Cyborg") + final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg") + else + for(var/obj/item/I in D) + qdel(I) + randomize_human(D) + JB.equip(D, TRUE, FALSE) + COMPILE_OVERLAYS(D) + var/icon/I = icon(getFlatIcon(D), frame = 1) + final.Insert(I, JB.title) + qdel(D) + //Also add the x + for(var/x_number in 1 to 4) + final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]") + fcopy(final, "icons/mob/landmarks.dmi") + +/client/proc/debug_z_levels() + set name = "Debug Z-Levels" + set category = "Mapping" + + var/list/z_list = SSmapping.z_list + var/list/messages = list() + messages += "World: [world.maxx] x [world.maxy] x [world.maxz]
    " + + var/list/linked_levels = list() + var/min_x = INFINITY + var/min_y = INFINITY + var/max_x = -INFINITY + var/max_y = -INFINITY + + for(var/z in 1 to max(world.maxz, z_list.len)) + if (z > z_list.len) + messages += "[z]: Unmanaged (out of bounds)
    " + continue + var/datum/space_level/S = z_list[z] + if (!S) + messages += "[z]: Unmanaged (null)
    " + continue + var/linkage + switch (S.linkage) + if (UNAFFECTED) + linkage = "no linkage" + if (SELFLOOPING) + linkage = "self-looping" + if (CROSSLINKED) + linkage = "linked at ([S.xi], [S.yi])" + linked_levels += S + min_x = min(min_x, S.xi) + min_y = min(min_y, S.yi) + max_x = max(max_x, S.xi) + max_y = max(max_y, S.yi) + else + linkage = "unknown linkage '[S.linkage]'" + + messages += "[z]: [S.name], [linkage], traits: [json_encode(S.traits)]
    " + if (S.z_value != z) + messages += "-- z_value is [S.z_value], should be [z]
    " + if (S.name == initial(S.name)) + messages += "-- name not set
    " + if (z > world.maxz) + messages += "-- exceeds max z" + + var/grid[max_x - min_x + 1][max_y - min_y + 1] + for(var/datum/space_level/S in linked_levels) + grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value + + messages += "" + for(var/y in max_y to min_y step -1) + var/list/part = list() + for(var/x in min_x to max_x) + part += "[grid[x - min_x + 1][y - min_y + 1]]" + messages += "" + messages += "
    [part.Join("")]
    " + + to_chat(src, messages.Join("")) diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index 4998db56..899b6212 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -57,10 +57,10 @@ if(item.parent) var/static/pipenetwarnings = 10 if(pipenetwarnings > 0) - warning("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z])") + log_mapping("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z]).") pipenetwarnings -= 1 if(pipenetwarnings == 0) - warning("build_pipeline(): further messages about pipenets will be suppressed") + log_mapping("build_pipeline(): further messages about pipenets will be suppressed") members += item possible_expansions += item diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm index 5853bc72..9870a895 100644 --- a/code/modules/atmospherics/machinery/other/meter.dm +++ b/code/modules/atmospherics/machinery/other/meter.dm @@ -1,147 +1,147 @@ -/obj/machinery/meter - name = "gas flow meter" - desc = "It measures something." - icon = 'icons/obj/atmospherics/pipes/meter.dmi' - icon_state = "meterX" - layer = GAS_PUMP_LAYER - power_channel = ENVIRON - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - max_integrity = 150 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) - var/frequency = 0 - var/atom/target - var/id_tag - var/target_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/meter/atmos - frequency = FREQ_ATMOS_STORAGE - -/obj/machinery/meter/atmos/atmos_waste_loop - name = "waste loop gas flow meter" - id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE - -/obj/machinery/meter/atmos/distro_loop - name = "distribution loop gas flow meter" - id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION - -/obj/machinery/meter/Destroy() - SSair.atmos_machinery -= src - target = null - return ..() - -/obj/machinery/meter/Initialize(mapload, new_piping_layer) - if(!isnull(new_piping_layer)) - target_layer = new_piping_layer - SSair.atmos_machinery += src - if(!target) - reattach_to_layer() - return ..() - -/obj/machinery/meter/proc/reattach_to_layer() - var/obj/machinery/atmospherics/candidate - for(var/obj/machinery/atmospherics/pipe/pipe in loc) - if(pipe.piping_layer == target_layer) - candidate = pipe - if(pipe.level == 2) - break - if(candidate) - target = candidate - setAttachLayer(candidate.piping_layer) - -/obj/machinery/meter/proc/setAttachLayer(new_layer) - target_layer = new_layer - PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) - -/obj/machinery/meter/process_atmos() - if(!target) - icon_state = "meterX" - return 0 - - if(stat & (BROKEN|NOPOWER)) - icon_state = "meter0" - return 0 - - use_power(5) - - 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(list( - "id_tag" = id_tag, - "device" = "AM", - "pressure" = round(env_pressure), - "sigtype" = "status" - )) - radio_connection.post_signal(src, signal) - -/obj/machinery/meter/proc/status() - if (target) - var/datum/gas_mixture/environment = target.return_air() - if(environment) - . = "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 - . = "The sensor error light is blinking." - else - . = "The connect error light is blinking." - -/obj/machinery/meter/examine(mob/user) - . = ..() - . += status() - -/obj/machinery/meter/wrench_act(mob/user, obj/item/I) - to_chat(user, "You begin to unfasten \the [src]...") - if (I.use_tool(src, user, 40, volume=50)) - user.visible_message( - "[user] unfastens \the [src].", - "You unfasten \the [src].", - "You hear ratchet.") - deconstruct() - return TRUE - -/obj/machinery/meter/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/pipe_meter(loc) - qdel(src) - -/obj/machinery/meter/interact(mob/user) - if(stat & (NOPOWER|BROKEN)) - return - else - to_chat(user, status()) - -/obj/machinery/meter/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -// TURF METER - REPORTS A TILE'S AIR CONTENTS -// why are you yelling? -/obj/machinery/meter/turf - -/obj/machinery/meter/turf/reattach_to_layer() - target = loc +/obj/machinery/meter + name = "gas flow meter" + desc = "It measures something." + icon = 'icons/obj/atmospherics/pipes/meter.dmi' + icon_state = "meterX" + layer = GAS_PUMP_LAYER + power_channel = ENVIRON + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + max_integrity = 150 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) + var/frequency = 0 + var/atom/target + var/id_tag + var/target_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/meter/atmos + frequency = FREQ_ATMOS_STORAGE + +/obj/machinery/meter/atmos/atmos_waste_loop + name = "waste loop gas flow meter" + id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE + +/obj/machinery/meter/atmos/distro_loop + name = "distribution loop gas flow meter" + id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION + +/obj/machinery/meter/Destroy() + SSair.atmos_machinery -= src + target = null + return ..() + +/obj/machinery/meter/Initialize(mapload, new_piping_layer) + if(!isnull(new_piping_layer)) + target_layer = new_piping_layer + SSair.atmos_machinery += src + if(!target) + reattach_to_layer() + return ..() + +/obj/machinery/meter/proc/reattach_to_layer() + var/obj/machinery/atmospherics/candidate + for(var/obj/machinery/atmospherics/pipe/pipe in loc) + if(pipe.piping_layer == target_layer) + candidate = pipe + if(pipe.level == 2) + break + if(candidate) + target = candidate + setAttachLayer(candidate.piping_layer) + +/obj/machinery/meter/proc/setAttachLayer(new_layer) + target_layer = new_layer + PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) + +/obj/machinery/meter/process_atmos() + if(!(target?.flags_1 & INITIALIZED_1)) + icon_state = "meterX" + return 0 + + if(stat & (BROKEN|NOPOWER)) + icon_state = "meter0" + return 0 + + use_power(5) + + 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(list( + "id_tag" = id_tag, + "device" = "AM", + "pressure" = round(env_pressure), + "sigtype" = "status" + )) + radio_connection.post_signal(src, signal) + +/obj/machinery/meter/proc/status() + if (target) + var/datum/gas_mixture/environment = target.return_air() + if(environment) + . = "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 + . = "The sensor error light is blinking." + else + . = "The connect error light is blinking." + +/obj/machinery/meter/examine(mob/user) + . = ..() + . += status() + +/obj/machinery/meter/wrench_act(mob/user, obj/item/I) + to_chat(user, "You begin to unfasten \the [src]...") + if (I.use_tool(src, user, 40, volume=50)) + user.visible_message( + "[user] unfastens \the [src].", + "You unfasten \the [src].", + "You hear ratchet.") + deconstruct() + return TRUE + +/obj/machinery/meter/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/pipe_meter(loc) + qdel(src) + +/obj/machinery/meter/interact(mob/user) + if(stat & (NOPOWER|BROKEN)) + return + else + to_chat(user, status()) + +/obj/machinery/meter/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +// TURF METER - REPORTS A TILE'S AIR CONTENTS +// why are you yelling? +/obj/machinery/meter/turf + +/obj/machinery/meter/turf/reattach_to_layer() + target = loc diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index 3009e5ee..f4df6dc6 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -59,9 +59,7 @@ return var/area/AS = get_area(src) if(istype(AS, /area/holodeck)) - log_world("### MAPPING ERROR") - log_world("Holodeck computer cannot be in a holodeck.") - log_world("This would cause circular power dependency.") + log_mapping("Holodeck computer cannot be in a holodeck, This would cause circular power dependency.") qdel(src) return else diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm index 1f55f188..3e361179 100644 --- a/code/modules/mapping/map_template.dm +++ b/code/modules/mapping/map_template.dm @@ -70,7 +70,7 @@ //initialize things that are normally initialized after map load parsed.initTemplateBounds() smooth_zlevel(world.maxz) - log_game("Z-level [name] loaded at at [x],[y],[world.maxz]") + log_game("Z-level [name] loaded at [x],[y],[world.maxz]") return level @@ -84,6 +84,13 @@ if(T.y+height > world.maxy) return + var/list/border = block(locate(max(T.x-1, 1), max(T.y-1, 1), T.z), + locate(min(T.x+width+1, world.maxx), min(T.y+height+1, world.maxy), T.z)) + for(var/L in border) + var/turf/turf_to_disable = L + SSair.remove_from_active(turf_to_disable) //stop processing turfs along the border to prevent runtimes, we return it in initTemplateBounds() + turf_to_disable.atmos_adjacent_turfs?.Cut() + // Accept cached maps, but don't save them automatically - we don't want // ruins clogging up memory for the whole round. var/datum/parsed_map/parsed = cached_map || new(file(mappath)) @@ -100,7 +107,7 @@ //initialize things that are normally initialized after map load parsed.initTemplateBounds() - log_game("[name] loaded at at [T.x],[T.y],[T.z]") + log_game("[name] loaded at [T.x],[T.y],[T.z]") return bounds /datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE) diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index 7a875ccf..5f0df36d 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -18,7 +18,7 @@ /obj/effect/baseturf_helper/LateInitialize() if(!baseturf_to_replace) - baseturf_to_replace = typecacheof(/turf/open/space) + baseturf_to_replace = typecacheof(list(/turf/open/space,/turf/baseturf_bottom)) else if(!length(baseturf_to_replace)) baseturf_to_replace = list(baseturf_to_replace = TRUE) else if(baseturf_to_replace[baseturf_to_replace[1]] != TRUE) // It's not associative @@ -45,7 +45,6 @@ thing.PlaceOnBottom(null, baseturf) else if(baseturf_to_replace[thing.baseturfs]) thing.assemble_baseturfs(baseturf) - return else thing.PlaceOnBottom(null, baseturf) @@ -107,16 +106,16 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper/Initialize(mapload) . = ..() if(!mapload) - log_world("### MAP WARNING, [src] spawned outside of mapload!") + log_mapping("[src] spawned outside of mapload!") return var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc if(airlock) if(airlock.cyclelinkeddir) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!") + log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!") else airlock.cyclelinkeddir = dir else - log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]") + log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") /obj/effect/mapping_helpers/airlock/locked @@ -126,16 +125,16 @@ /obj/effect/mapping_helpers/airlock/locked/Initialize(mapload) . = ..() if(!mapload) - log_world("### MAP WARNING, [src] spawned outside of mapload!") + log_mapping("[src] spawned outside of mapload!") return var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc if(airlock) if(airlock.locked) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!") + log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!") else airlock.locked = TRUE else - log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]") + log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") /obj/effect/mapping_helpers/airlock/unres name = "airlock unresctricted side helper" @@ -144,13 +143,13 @@ /obj/effect/mapping_helpers/airlock/unres/Initialize(mapload) . = ..() if(!mapload) - log_world("### MAP WARNING, [src] spawned outside of mapload!") + log_mapping("[src] spawned outside of mapload!") return var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc if(airlock) airlock.unres_sides ^= dir else - log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]") + log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") //needs to do its thing before spawn_rivers() is called @@ -164,17 +163,6 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) var/turf/T = get_turf(src) T.flags_1 |= NO_LAVA_GEN_1 -/// Adds the map it is on to the z_is_planet list -/obj/effect/mapping_helpers/planet_z - name = "planet z helper" - layer = POINT_LAYER - -/obj/effect/mapping_helpers/planet_z/Initialize() - . = ..() - var/datum/space_level/S = SSmapping.get_level(z) - S.traits[ZTRAIT_PLANET] = TRUE - - //This helper applies components to things on the map directly. /obj/effect/mapping_helpers/component_injector name = "Component Injector" diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm index e6fa2421..4b61663f 100644 --- a/code/modules/mapping/preloader.dm +++ b/code/modules/mapping/preloader.dm @@ -8,18 +8,26 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new) var/list/attributes var/target_path -/datum/map_preloader/proc/setup(list/the_attributes, path) +/world/proc/preloader_setup(list/the_attributes, path) if(the_attributes.len) GLOB.use_preloader = TRUE - attributes = the_attributes - target_path = path + var/datum/map_preloader/preloader_local = GLOB._preloader + preloader_local.attributes = the_attributes + preloader_local.target_path = path -/datum/map_preloader/proc/load(atom/what) +/world/proc/preloader_load(atom/what) GLOB.use_preloader = FALSE - for(var/attribute in attributes) - var/value = attributes[attribute] + var/datum/map_preloader/preloader_local = GLOB._preloader + for(var/attribute in preloader_local.attributes) + var/value = preloader_local.attributes[attribute] if(islist(value)) value = deepCopyList(value) + #ifdef TESTING + if(what.vars[attribute] == value) + var/message = "[what.type] at [AREACOORD(what)] - VAR: [attribute] = [isnull(value) ? "null" : (isnum(value) ? value : "\"[value]\"")]" + log_mapping("DIRTY VAR: [message]") + GLOB.dirty_vars += message + #endif what.vars[attribute] = value /area/template_noop diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index 22be4aa2..9b27691d 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -306,8 +306,8 @@ //first instance the /area and remove it from the members list index = members.len if(members[index] != /area/template_noop) - GLOB._preloader.setup(members_attributes[index])//preloader for assigning set variables on atom creation var/atype = members[index] + world.preloader_setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation var/atom/instance = areaCache[atype] if (!instance) instance = GLOB.areas_by_type[atype] @@ -318,7 +318,7 @@ instance.contents.Add(crds) if(GLOB.use_preloader && instance) - GLOB._preloader.load(instance) + world.preloader_load(instance) //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect @@ -354,7 +354,7 @@ //Instance an atom at (x,y,z) and gives it the variables in attributes /datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop) - GLOB._preloader.setup(attributes, path) + world.preloader_setup(attributes, path) if(crds) if(ispath(path, /turf)) @@ -368,7 +368,7 @@ . = create_atom(path, crds)//first preloader pass if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New() - GLOB._preloader.load(.) + world.preloader_load(.) //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize if(TICK_CHECK) diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm index 5875e821..d684b2ed 100644 --- a/code/modules/recycling/conveyor2.dm +++ b/code/modules/recycling/conveyor2.dm @@ -31,7 +31,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) /obj/machinery/conveyor/inverted/Initialize(mapload) . = ..() if(mapload && !(dir in GLOB.diagonals)) - log_game("### MAPPING ERROR: [src] at [AREACOORD(src)] spawned without using a diagonal dir. Please replace with a normal version.") + log_mapping("[src] at [AREACOORD(src)] spawned without using a diagonal dir. Please replace with a normal version.") // Auto conveyour is always on unless unpowered From 6dbdfa0c145d114bd21f71565acce8d3a6d4e164 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Fri, 17 Jan 2020 22:34:38 +0100 Subject: [PATCH 17/21] Merge pull request #10570 from kevinz000/map_network_helpers Power cable + auto mapping helpers --- .../_mapping_helpers.dm} | 87 - .../mapping/mapping_helpers/baseturf.dm | 82 + .../network_builder/_network_builder.dm | 43 + .../network_builder/atmos_pipe.dm | 96 + .../network_builder/power_cables.dm | 189 + code/modules/power/cable.dm | 1704 ++--- icons/effects/mapping_helpers.dmi | Bin 5656 -> 6878 bytes tgstation.dme | 6556 +++++++++-------- 8 files changed, 4544 insertions(+), 4213 deletions(-) rename code/modules/mapping/{mapping_helpers.dm => mapping_helpers/_mapping_helpers.dm} (59%) create mode 100644 code/modules/mapping/mapping_helpers/baseturf.dm create mode 100644 code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm create mode 100644 code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm create mode 100644 code/modules/mapping/mapping_helpers/network_builder/power_cables.dm diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm similarity index 59% rename from code/modules/mapping/mapping_helpers.dm rename to code/modules/mapping/mapping_helpers/_mapping_helpers.dm index 5f0df36d..68bafcc2 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm @@ -1,90 +1,4 @@ //Landmarks and other helpers which speed up the mapping process and reduce the number of unique instances/subtypes of items/turf/ect - - - -/obj/effect/baseturf_helper //Set the baseturfs of every turf in the /area/ it is placed. - name = "baseturf editor" - icon = 'icons/effects/mapping_helpers.dmi' - icon_state = "" - - var/list/baseturf_to_replace - var/baseturf - - layer = POINT_LAYER - -/obj/effect/baseturf_helper/Initialize() - . = ..() - return INITIALIZE_HINT_LATELOAD - -/obj/effect/baseturf_helper/LateInitialize() - if(!baseturf_to_replace) - baseturf_to_replace = typecacheof(list(/turf/open/space,/turf/baseturf_bottom)) - else if(!length(baseturf_to_replace)) - baseturf_to_replace = list(baseturf_to_replace = TRUE) - else if(baseturf_to_replace[baseturf_to_replace[1]] != TRUE) // It's not associative - var/list/formatted = list() - for(var/i in baseturf_to_replace) - formatted[i] = TRUE - baseturf_to_replace = formatted - - var/area/our_area = get_area(src) - for(var/i in get_area_turfs(our_area, z)) - replace_baseturf(i) - - qdel(src) - -/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing) - var/list/baseturf_cache = thing.baseturfs - if(length(baseturf_cache)) - for(var/i in baseturf_cache) - if(baseturf_to_replace[i]) - baseturf_cache -= i - if(!baseturf_cache.len) - thing.assemble_baseturfs(baseturf) - else - thing.PlaceOnBottom(null, baseturf) - else if(baseturf_to_replace[thing.baseturfs]) - thing.assemble_baseturfs(baseturf) - else - thing.PlaceOnBottom(null, baseturf) - -/obj/effect/baseturf_helper/space - name = "space baseturf editor" - baseturf = /turf/open/space - -/obj/effect/baseturf_helper/asteroid - name = "asteroid baseturf editor" - baseturf = /turf/open/floor/plating/asteroid - -/obj/effect/baseturf_helper/asteroid/airless - name = "asteroid airless baseturf editor" - baseturf = /turf/open/floor/plating/asteroid/airless - -/obj/effect/baseturf_helper/asteroid/basalt - name = "asteroid basalt baseturf editor" - baseturf = /turf/open/floor/plating/asteroid/basalt - -/obj/effect/baseturf_helper/asteroid/snow - name = "asteroid snow baseturf editor" - baseturf = /turf/open/floor/plating/asteroid/snow - -/obj/effect/baseturf_helper/beach/sand - name = "beach sand baseturf editor" - baseturf = /turf/open/floor/plating/beach/sand - -/obj/effect/baseturf_helper/beach/water - name = "water baseturf editor" - baseturf = /turf/open/floor/plating/beach/water - -/obj/effect/baseturf_helper/lava - name = "lava baseturf editor" - baseturf = /turf/open/lava/smooth - -/obj/effect/baseturf_helper/lava_land/surface - name = "lavaland baseturf editor" - baseturf = /turf/open/lava/smooth/lava_land_surface - - /obj/effect/mapping_helpers icon = 'icons/effects/mapping_helpers.dmi' icon_state = "" @@ -94,7 +8,6 @@ ..() return late ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_QDEL - //airlock helpers /obj/effect/mapping_helpers/airlock layer = DOOR_HELPER_LAYER diff --git a/code/modules/mapping/mapping_helpers/baseturf.dm b/code/modules/mapping/mapping_helpers/baseturf.dm new file mode 100644 index 00000000..f4bd0d7c --- /dev/null +++ b/code/modules/mapping/mapping_helpers/baseturf.dm @@ -0,0 +1,82 @@ +/obj/effect/baseturf_helper //Set the baseturfs of every turf in the /area/ it is placed. + name = "baseturf editor" + icon = 'icons/effects/mapping_helpers.dmi' + icon_state = "" + + var/list/baseturf_to_replace + var/baseturf + + layer = POINT_LAYER + +/obj/effect/baseturf_helper/Initialize() + . = ..() + return INITIALIZE_HINT_LATELOAD + +/obj/effect/baseturf_helper/LateInitialize() + if(!baseturf_to_replace) + baseturf_to_replace = typecacheof(list(/turf/open/space,/turf/baseturf_bottom)) + else if(!length(baseturf_to_replace)) + baseturf_to_replace = list(baseturf_to_replace = TRUE) + else if(baseturf_to_replace[baseturf_to_replace[1]] != TRUE) // It's not associative + var/list/formatted = list() + for(var/i in baseturf_to_replace) + formatted[i] = TRUE + baseturf_to_replace = formatted + + var/area/our_area = get_area(src) + for(var/i in get_area_turfs(our_area, z)) + replace_baseturf(i) + + qdel(src) + +/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing) + var/list/baseturf_cache = thing.baseturfs + if(length(baseturf_cache)) + for(var/i in baseturf_cache) + if(baseturf_to_replace[i]) + baseturf_cache -= i + if(!baseturf_cache.len) + thing.assemble_baseturfs(baseturf) + else + thing.PlaceOnBottom(null, baseturf) + else if(baseturf_to_replace[thing.baseturfs]) + thing.assemble_baseturfs(baseturf) + else + thing.PlaceOnBottom(null, baseturf) + +/obj/effect/baseturf_helper/space + name = "space baseturf editor" + baseturf = /turf/open/space + +/obj/effect/baseturf_helper/asteroid + name = "asteroid baseturf editor" + baseturf = /turf/open/floor/plating/asteroid + +/obj/effect/baseturf_helper/asteroid/airless + name = "asteroid airless baseturf editor" + baseturf = /turf/open/floor/plating/asteroid/airless + +/obj/effect/baseturf_helper/asteroid/basalt + name = "asteroid basalt baseturf editor" + baseturf = /turf/open/floor/plating/asteroid/basalt + +/obj/effect/baseturf_helper/asteroid/snow + name = "asteroid snow baseturf editor" + baseturf = /turf/open/floor/plating/asteroid/snow + +/obj/effect/baseturf_helper/beach/sand + name = "beach sand baseturf editor" + baseturf = /turf/open/floor/plating/beach/sand + +/obj/effect/baseturf_helper/beach/water + name = "water baseturf editor" + baseturf = /turf/open/floor/plating/beach/water + +/obj/effect/baseturf_helper/lava + name = "lava baseturf editor" + baseturf = /turf/open/lava/smooth + +/obj/effect/baseturf_helper/lava_land/surface + name = "lavaland baseturf editor" + baseturf = /turf/open/lava/smooth/lava_land_surface + diff --git a/code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm b/code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm new file mode 100644 index 00000000..78a835cf --- /dev/null +++ b/code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm @@ -0,0 +1,43 @@ +//Builds networks like power cables/atmos lines/etc +//Just a holder parent type for now.. +/obj/effect/mapping_helpers/network_builder + /// set var to true to not del on lateload + var/custom_spawned = FALSE + + icon = 'icons/effects/mapping_helpers.dmi' + + late = TRUE + /// what directions we know connections are in + var/list/network_directions = list() + +/obj/effect/mapping_helpers/network_builder/Initialize(mapload) + . = ..() + to_chat(world, "DEBUG: Initializing [COORD(src)]") + var/conflict = check_duplicates() + if(conflict) + stack_trace("WARNING: [type] network building helper found check_duplicates() conflict [conflict] in its location.!") + return INITIALIZE_HINT_QDEL + if(!mapload) + if(GLOB.Debug2) + custom_spawned = TRUE + return INITIALIZE_HINT_NORMAL + else + return INITIALIZE_HINT_QDEL + return INITIALIZE_HINT_LATELOAD + +/// How this works: On LateInitialize, detect all directions that this should be applicable to, and do what it needs to do, and then inform all network builders in said directions that it's been around since it won't be around afterwards. +/obj/effect/mapping_helpers/network_builder/LateInitialize() + to_chat(world, "DEBUG: LateInitializing [COORD(src)]") + scan_directions() + build_network() + if(!custom_spawned) + qdel(src) + +/obj/effect/mapping_helpers/network_builder/proc/check_duplicates() + CRASH("Base abstract network builder tried to check duplicates.") + +/obj/effect/mapping_helpers/network_builder/proc/scan_directions() + CRASH("Base abstract network builder tried to scan directions.") + +/obj/effect/mapping_helpers/network_builder/proc/build_network() + CRASH("Base abstract network builder tried to build network.") diff --git a/code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm b/code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm new file mode 100644 index 00000000..1983bab3 --- /dev/null +++ b/code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm @@ -0,0 +1,96 @@ +/* Automatically places pipes on init based on any pipes connecting to it and adjacent helpers. Only supports cardinals. + * Conflicts with ANY PIPE ON ITS LAYER, as well as atmos network build helpers on the same layer, as well as any pipe on all layers. Do those manually. +*/ +/obj/effect/mapping_helpers/network_builder/atmos_pipe + name = "atmos pipe autobuilder" + icon_state = "atmospipebuilder" + + /// Layer to put our pipes on + var/pipe_layer = PIPING_LAYER_DEFAULT + + /// Color to set our pipes to + var/pipe_color + + /// Whether or not pipes we make are visible + var/visible_pipes = FALSE + + color = null + +/obj/effect/mapping_helpers/network_builder/atmos_pipe/check_duplicates() + for(var/obj/effect/mapping_helpers/network_builder/atmos_pipe/other in loc) + if(other == src) + continue + if(other.pipe_layer == pipe_layer) + return other + for(var/obj/machinery/atmospherics/A in loc) + if(A.pipe_flags & PIPING_ALL_LAYER) + return A + if(A.piping_layer == pipe_layer) + return A + return FALSE + +/// Scans directions, sets network_directions to have every direction that we can link to. If there's another power cable builder detected, make sure they know we're here by adding us to their cable directions list before we're deleted. +/obj/effect/mapping_helpers/network_builder/atmos_pipe/scan_directions() + var/turf/T + for(var/i in GLOB.cardinals) + if(i in network_directions) + continue //we're already set, that means another builder set us. + T = get_step(loc, i) + if(!T) + continue + var/found = FALSE + for(var/obj/effect/mapping_helpers/network_builder/atmos_pipe/other in T) + if(other.pipe_layer == pipe_layer) + network_directions += i + other.network_directions += turn(i, 180) + found = TRUE + break + if(found) + continue + for(var/obj/machinery/atmospherics/A in T) + if((A.piping_layer == pipe_layer) && (A.initialize_directions & turn(i, 180))) + network_directions += i + break + return network_directions + +/// Directions should only ever have cardinals. +/obj/effect/mapping_helpers/network_builder/atmos_pipe/build_network() + if(length(network_directions) <= 1) + return + var/obj/machinery/atmospherics/pipe/built + switch(length(network_directions)) + if(2) //straight pipe + built = new /obj/machinery/atmospherics/pipe/simple(loc) + var/d1 = network_directions[1] + var/d2 = network_directions[2] + var/combined = d1 | d2 + if(combined in GLOB.diagonals) + built.setDir(combined) + else + built.setDir(d1) + if(3) //manifold + var/list/missing = network_directions ^ GLOB.cardinals + missing = missing[1] + built = new /obj/machinery/atmospherics/pipe/manifold(loc) + built.setDir(missing) + if(4) //4 way manifold + built = new /obj/machinery/atmospherics/pipe/manifold4w(loc) + built.SetInitDirections() + built.on_construction(pipe_color, pipe_layer) + built.hide(!visible_pipes) + +/obj/effect/mapping_helpers/network_builder/atmos_pipe/distro + name = "distro line autobuilder" + pipe_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + pipe_color = rgb(130,43,255) + color = rgb(130,43,255) + +/obj/effect/mapping_helpers/network_builder/atmos_pipe/scrubbers + name = "scrubbers line autobuilder" + pipe_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + pipe_color = rgb(255,0,0) + color = rgb(255,0,0) diff --git a/code/modules/mapping/mapping_helpers/network_builder/power_cables.dm b/code/modules/mapping/mapping_helpers/network_builder/power_cables.dm new file mode 100644 index 00000000..53850456 --- /dev/null +++ b/code/modules/mapping/mapping_helpers/network_builder/power_cables.dm @@ -0,0 +1,189 @@ +#define NO_KNOT 0 +#define KNOT_AUTO 1 +#define KNOT_FORCED 2 + +/// Automatically links on init to power cables and other cable builder helpers. Only supports cardinals. +/obj/effect/mapping_helpers/network_builder/power_cable + name = "power line autobuilder" + icon_state = "powerlinebuilder" + + color = "#ff0000" + + /// Whether or not we forcefully make a knot + var/knot = NO_KNOT + + /// cable color as from GLOB.cable_colors + var/cable_color = "red" + +/obj/effect/mapping_helpers/network_builder/power_cable/check_duplicates() + var/obj/structure/cable/C = locate() in loc + if(C) + return C + for(var/obj/effect/mapping_helpers/network_builder/power_cable/other in loc) + if(other == src) + continue + return other + +/// Scans directions, sets network_directions to have every direction that we can link to. If there's another power cable builder detected, make sure they know we're here by adding us to their cable directions list before we're deleted. +/obj/effect/mapping_helpers/network_builder/power_cable/scan_directions() + var/turf/T + for(var/i in GLOB.cardinals) + if(i in network_directions) + continue //we're already set, that means another builder set us. + T = get_step(loc, i) + if(!T) + continue + var/obj/effect/mapping_helpers/network_builder/power_cable/other = locate() in T + if(other) + network_directions += i + other.network_directions += turn(i, 180) + continue + for(var/obj/structure/cable/C in T) + if(C.d1 == turn(i, 180) || C.d2 == turn(i, 180)) + network_directions += i + continue + return network_directions + +/// Directions should only ever have cardinals. +/obj/effect/mapping_helpers/network_builder/power_cable/build_network() + if(!length(network_directions)) + return + else if(length(network_directions) == 1) + new /obj/structure/cable(loc, cable_color, NONE, network_directions[1]) + else + if(knot == KNOT_FORCED) + for(var/d in network_directions) + new /obj/structure/cable(loc, cable_color, NONE, d) + else + var/do_knot = (knot == KNOT_FORCED) || ((knot == KNOT_AUTO) && should_auto_knot()) + var/dirs = length(network_directions) + for(var/i in 1 to dirs - 1) + var/li = (i == 1)? dirs : (i - 1) + var/d1 = network_directions[i] + var/d2 = network_directions[li] + if(d1 > d2) //this is ugly please help me + d1 = network_directions[li] + d2 = network_directions[i] + new /obj/structure/cable(loc, cable_color, d1, d2) + if(do_knot) + new /obj/structure/cable(loc, cable_color, NONE, network_directions[i]) + do_knot = FALSE + +/obj/effect/mapping_helpers/network_builder/power_cable/proc/should_auto_knot() + return (locate(/obj/machinery/power/terminal) in loc) + +/obj/effect/mapping_helpers/network_builder/power_cable/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// Red +/obj/effect/mapping_helpers/network_builder/power_cable/red + color = "#ff0000" + cable_color = "red" + +/obj/effect/mapping_helpers/network_builder/power_cable/red/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/red/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// White +/obj/effect/mapping_helpers/network_builder/power_cable/white + color = "#ffffff" + cable_color = "white" + +/obj/effect/mapping_helpers/network_builder/power_cable/white/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/white/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// Cyan +/obj/effect/mapping_helpers/network_builder/power_cable/cyan + color = "#00ffff" + cable_color = "cyan" + +/obj/effect/mapping_helpers/network_builder/power_cable/cyan/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/cyan/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// Orange +/obj/effect/mapping_helpers/network_builder/power_cable/orange + color = "#ff8000" + cable_color = "orange" + +/obj/effect/mapping_helpers/network_builder/power_cable/orange/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/orange/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// Pink +/obj/effect/mapping_helpers/network_builder/power_cable/pink + color = "#ff3cc8" + cable_color = "pink" + +/obj/effect/mapping_helpers/network_builder/power_cable/pink/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/pink/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// Blue +/obj/effect/mapping_helpers/network_builder/power_cable/blue + color = "#1919c8" + cable_color = "blue" + +/obj/effect/mapping_helpers/network_builder/power_cable/blue/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/blue/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// Green +/obj/effect/mapping_helpers/network_builder/power_cable/green + color = "#00aa00" + cable_color = "green" + +/obj/effect/mapping_helpers/network_builder/power_cable/green/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/green/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +// Yellow +/obj/effect/mapping_helpers/network_builder/power_cable/yellow + color = "#ffff00" + cable_color = "yellow" + +/obj/effect/mapping_helpers/network_builder/power_cable/yellow/knot + icon_state = "powerlinebuilderknot" + knot = KNOT_FORCED + +/obj/effect/mapping_helpers/network_builder/power_cable/yellow/auto + icon_state = "powerlinebuilderauto" + knot = KNOT_AUTO + +#undef NO_KNOT +#undef KNOT_AUTO +#undef KNOT_FORCED diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index d4ca9a16..3c926ef4 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -1,851 +1,855 @@ -GLOBAL_LIST_INIT(cable_colors, list( - "yellow" = "#ffff00", - "green" = "#00aa00", - "blue" = "#1919c8", - "pink" = "#ff3cc8", - "orange" = "#ff8000", - "cyan" = "#00ffff", - "white" = "#ffffff", - "red" = "#ff0000" - )) - -/////////////////////////////// -//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 - name = "power cable" - desc = "A flexible, superconducting insulated cable for heavy-duty power transfer." - icon = 'icons/obj/power_cond/cables.dmi' - icon_state = "0-1" - level = 1 //is underfloor - layer = WIRE_LAYER //Above hidden pipes, GAS_PIPE_HIDDEN_LAYER - anchored = TRUE - obj_flags = CAN_BE_HIT | ON_BLUEPRINTS - var/d1 = 0 // cable direction 1 (see above) - var/d2 = 1 // cable direction 2 (see above) - var/datum/powernet/powernet - var/obj/item/stack/cable_coil/stored - - var/cable_color = "red" - color = "#ff0000" - -/obj/structure/cable/yellow - cable_color = "yellow" - color = "#ffff00" - -/obj/structure/cable/green - cable_color = "green" - color = "#00aa00" - -/obj/structure/cable/blue - cable_color = "blue" - color = "#1919c8" - -/obj/structure/cable/pink - cable_color = "pink" - color = "#ff3cc8" - -/obj/structure/cable/orange - cable_color = "orange" - color = "#ff8000" - -/obj/structure/cable/cyan - cable_color = "cyan" - color = "#00ffff" - -/obj/structure/cable/white - cable_color = "white" - color = "#ffffff" - -// the power cable object -/obj/structure/cable/Initialize(mapload, param_color) - . = ..() - - // 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) - GLOB.cable_list += src //add it to the global cable list - - if(d1) - stored = new/obj/item/stack/cable_coil(null,2,cable_color) - else - stored = new/obj/item/stack/cable_coil(null,1,cable_color) - - var/list/cable_colors = GLOB.cable_colors - cable_color = param_color || cable_color || pick(cable_colors) - if(cable_colors[cable_color]) - cable_color = cable_colors[cable_color] - update_icon() - -/obj/structure/cable/Destroy() // called when a cable is deleted - if(powernet) - cut_cable_from_powernet() // update the powernets - 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_1 & NODECONSTRUCT_1)) - var/turf/T = loc - stored.forceMove(T) - qdel(src) - -/////////////////////////////////// -// General procedures -/////////////////////////////////// - -//If underfloor, hide the cable -/obj/structure/cable/hide(i) - - if(level == 1 && isturf(loc)) - invisibility = i ? INVISIBILITY_MAXIMUM : 0 - update_icon() - -/obj/structure/cable/update_icon() - icon_state = "[d1]-[d2]" - color = null - add_atom_colour(cable_color, FIXED_COLOUR_PRIORITY) - -/obj/structure/cable/proc/handlecable(obj/item/W, mob/user, params) - var/turf/T = get_turf(src) - if(T.intact) - return - if(istype(W, /obj/item/wirecutters)) - if (shock(user, 50)) - return - user.visible_message("[user] cuts the cable.", "You cut the cable.") - stored.add_fingerprint(user) - investigate_log("was cut by [key_name(usr)] in [AREACOORD(src)]", INVESTIGATE_WIRES) - deconstruct() - 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/multitool)) - if(powernet && (powernet.avail > 0)) // is it powered? - to_chat(user, "[DisplayPower(powernet.avail)] in power network.") - else - to_chat(user, "The cable is not powered.") - shock(user, 5, 0.2) - - src.add_fingerprint(user) - -// 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, params) - handlecable(W, user, params) - - -// shock the user with probability prb -/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1) - if(!prob(prb)) - return 0 - if (electrocute_mob(user, powernet, src, siemens_coeff)) - do_sparks(5, TRUE, src) - return 1 - else - return 0 - -/obj/structure/cable/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -/obj/structure/cable/proc/update_stored(length = 1, colorC = "red") - stored.amount = length - stored.item_color = colorC - stored.update_icon() - -//////////////////////////////////////////// -// 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 - -///////////////////////////////////////////////// -// 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) - moveToNullspace() - powernet.remove_cable(src) //remove the cut cable from its powernet - - 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" - gender = NEUTER //That's a cable coil sounds better than that's some cable coils - icon = 'icons/obj/power.dmi' - icon_state = "coil" - item_state = "coil" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - max_amount = MAXCOIL - amount = MAXCOIL - merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves - item_color = "red" - desc = "A coil of insulated power cable." - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - materials = list(MAT_METAL=10, MAT_GLASS=5) - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - attack_verb = list("whipped", "lashed", "disciplined", "flogged") - singular_name = "cable piece" - full_w_class = WEIGHT_CLASS_SMALL - grind_results = list(/datum/reagent/copper = 2) //2 copper per cable in the coil - usesound = 'sound/items/deconstruct.ogg' - -/obj/item/stack/cable_coil/cyborg - is_cyborg = 1 - materials = list() - cost = 1 - -/obj/item/stack/cable_coil/cyborg/attack_self(mob/user) - var/cable_color = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") - item_color = cable_color - update_icon() - -/obj/item/stack/cable_coil/suicide_act(mob/user) - if(locate(/obj/structure/chair/stool) in get_turf(user)) - user.visible_message("[user] is making a noose with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - else - user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return(OXYLOSS) - -/obj/item/stack/cable_coil/Initialize(mapload, new_amount = null, param_color = null) - . = ..() - - var/list/cable_colors = GLOB.cable_colors - item_color = param_color || item_color || pick(cable_colors) - if(cable_colors[item_color]) - item_color = cable_colors[item_color] - - pixel_x = rand(-2,2) - pixel_y = rand(-2,2) - update_icon() - recipes = GLOB.cable_coil_recipes - -/////////////////////////////////// -// General procedures -/////////////////////////////////// - - -//you can use wires to heal robotics -/obj/item/stack/cable_coil/attack(mob/living/carbon/human/H, mob/user) - if(!istype(H)) - return ..() - - var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected)) - if(affecting && affecting.status == BODYPART_ROBOTIC) - if(user == H) - user.visible_message("[user] starts to fix some of the wires in [H]'s [affecting.name].", "You start fixing some of the wires in [H]'s [affecting.name].") - if(!do_mob(user, H, 50)) - return - if(item_heal_robotic(H, user, 0, 15)) - use(1) - return - else - return ..() - - -/obj/item/stack/cable_coil/update_icon() - icon_state = "[initial(item_state)][amount < 3 ? amount : ""]" - name = "cable [amount < 3 ? "piece" : "coil"]" - color = null - add_atom_colour(item_color, FIXED_COLOUR_PRIORITY) - -/obj/item/stack/cable_coil/attack_hand(mob/user) - . = ..() - if(.) - return - var/obj/item/stack/cable_coil/new_cable = ..() - if(istype(new_cable)) - new_cable.item_color = item_color - new_cable.update_icon() - -//add cables to the stack -/obj/item/stack/cable_coil/proc/give(extra) - if(amount + extra > max_amount) - amount = max_amount - else - amount += extra - update_icon() - - - -/////////////////////////////////////////////// -// Cable laying procedures -////////////////////////////////////////////// - -/obj/item/stack/cable_coil/proc/get_new_cable(location) - var/path = /obj/structure/cable - return new path(location, item_color) - -// called when cable_coil is clicked on a turf -/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.update_icon() - - //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, 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, showerror = TRUE, forceddir) - var/turf/U = user.loc - if(!isturf(U)) - return - - var/turf/T = C.loc - - 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 && !forceddir) //if clicked on the turf we're standing on and a direction wasn't supplied, 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 and no direction was supplied - if((C.d1 == dirn || C.d2 == dirn) && !forceddir) - if(!U.can_have_cabling()) //checking if it's a plating or catwalk - if (showerror) - to_chat(user, "You can only lay cables on catwalks and plating!") - return - if(U.intact) //can't place a cable if it's a plating with a tile on it - 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) - if (showerror) - 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 or we have a supplied direction, 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 - if (showerror) - to_chat(user, "There's already a cable at that position!") - - return - - - C.update_icon() - - C.d1 = nd1 - C.d2 = nd2 - - //updates the stored cable coil - C.update_stored(2, item_color) - - C.add_fingerprint(user) - C.update_icon() - - - 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/red - item_color = "red" - color = "#ff0000" - -/obj/item/stack/cable_coil/yellow - item_color = "yellow" - color = "#ffff00" - -/obj/item/stack/cable_coil/blue - item_color = "blue" - color = "#1919c8" - -/obj/item/stack/cable_coil/green - item_color = "green" - color = "#00aa00" - -/obj/item/stack/cable_coil/pink - item_color = "pink" - color = "#ff3ccd" - -/obj/item/stack/cable_coil/orange - item_color = "orange" - color = "#ff8000" - -/obj/item/stack/cable_coil/cyan - item_color = "cyan" - color = "#00ffff" - -/obj/item/stack/cable_coil/white - item_color = "white" - -/obj/item/stack/cable_coil/random - item_color = null - color = "#ffffff" - - -/obj/item/stack/cable_coil/random/five - amount = 5 - -/obj/item/stack/cable_coil/cut - amount = null - icon_state = "coil2" - -/obj/item/stack/cable_coil/cut/Initialize(mapload) - . = ..() - if(!amount) - amount = rand(1,2) - pixel_x = rand(-2,2) - pixel_y = rand(-2,2) - update_icon() - -/obj/item/stack/cable_coil/cut/red - item_color = "red" - color = "#ff0000" - -/obj/item/stack/cable_coil/cut/yellow - item_color = "yellow" - color = "#ffff00" - -/obj/item/stack/cable_coil/cut/blue - item_color = "blue" - color = "#1919c8" - -/obj/item/stack/cable_coil/cut/green - item_color = "green" - color = "#00aa00" - -/obj/item/stack/cable_coil/cut/pink - item_color = "pink" - color = "#ff3ccd" - -/obj/item/stack/cable_coil/cut/orange - item_color = "orange" - color = "#ff8000" - -/obj/item/stack/cable_coil/cut/cyan - item_color = "cyan" - color = "#00ffff" - -/obj/item/stack/cable_coil/cut/white - item_color = "white" - -/obj/item/stack/cable_coil/cut/random - item_color = null +GLOBAL_LIST_INIT(cable_colors, list( + "yellow" = "#ffff00", + "green" = "#00aa00", + "blue" = "#1919c8", + "pink" = "#ff3cc8", + "orange" = "#ff8000", + "cyan" = "#00ffff", + "white" = "#ffffff", + "red" = "#ff0000" + )) + +/////////////////////////////// +//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 + name = "power cable" + desc = "A flexible, superconducting insulated cable for heavy-duty power transfer." + icon = 'icons/obj/power_cond/cables.dmi' + icon_state = "0-1" + level = 1 //is underfloor + layer = WIRE_LAYER //Above hidden pipes, GAS_PIPE_HIDDEN_LAYER + anchored = TRUE + obj_flags = CAN_BE_HIT | ON_BLUEPRINTS + var/d1 = 0 // cable direction 1 (see above) + var/d2 = 1 // cable direction 2 (see above) + var/datum/powernet/powernet + var/obj/item/stack/cable_coil/stored + + var/cable_color = "red" + color = "#ff0000" + +/obj/structure/cable/yellow + cable_color = "yellow" + color = "#ffff00" + +/obj/structure/cable/green + cable_color = "green" + color = "#00aa00" + +/obj/structure/cable/blue + cable_color = "blue" + color = "#1919c8" + +/obj/structure/cable/pink + cable_color = "pink" + color = "#ff3cc8" + +/obj/structure/cable/orange + cable_color = "orange" + color = "#ff8000" + +/obj/structure/cable/cyan + cable_color = "cyan" + color = "#00ffff" + +/obj/structure/cable/white + cable_color = "white" + color = "#ffffff" + +// the power cable object +/obj/structure/cable/Initialize(mapload, param_color, _d1, _d2) + . = ..() + + if(isnull(_d1) || isnull(_d2)) + // 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 ) ) + else + d1 = _d1 + d2 = _d2 + + var/turf/T = get_turf(src) // hide if turf is not intact + if(level==1) + hide(T.intact) + GLOB.cable_list += src //add it to the global cable list + + if(d1) + stored = new/obj/item/stack/cable_coil(null,2,cable_color) + else + stored = new/obj/item/stack/cable_coil(null,1,cable_color) + + var/list/cable_colors = GLOB.cable_colors + cable_color = param_color || cable_color || pick(cable_colors) + if(cable_colors[cable_color]) + cable_color = cable_colors[cable_color] + update_icon() + +/obj/structure/cable/Destroy() // called when a cable is deleted + if(powernet) + cut_cable_from_powernet() // update the powernets + 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_1 & NODECONSTRUCT_1)) + var/turf/T = loc + stored.forceMove(T) + qdel(src) + +/////////////////////////////////// +// General procedures +/////////////////////////////////// + +//If underfloor, hide the cable +/obj/structure/cable/hide(i) + + if(level == 1 && isturf(loc)) + invisibility = i ? INVISIBILITY_MAXIMUM : 0 + update_icon() + +/obj/structure/cable/update_icon() + icon_state = "[d1]-[d2]" + color = null + add_atom_colour(cable_color, FIXED_COLOUR_PRIORITY) + +/obj/structure/cable/proc/handlecable(obj/item/W, mob/user, params) + var/turf/T = get_turf(src) + if(T.intact) + return + if(istype(W, /obj/item/wirecutters)) + if (shock(user, 50)) + return + user.visible_message("[user] cuts the cable.", "You cut the cable.") + stored.add_fingerprint(user) + investigate_log("was cut by [key_name(usr)] in [AREACOORD(src)]", INVESTIGATE_WIRES) + deconstruct() + 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/multitool)) + if(powernet && (powernet.avail > 0)) // is it powered? + to_chat(user, "[DisplayPower(powernet.avail)] in power network.") + else + to_chat(user, "The cable is not powered.") + shock(user, 5, 0.2) + + src.add_fingerprint(user) + +// 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, params) + handlecable(W, user, params) + + +// shock the user with probability prb +/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1) + if(!prob(prb)) + return 0 + if (electrocute_mob(user, powernet, src, siemens_coeff)) + do_sparks(5, TRUE, src) + return 1 + else + return 0 + +/obj/structure/cable/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +/obj/structure/cable/proc/update_stored(length = 1, colorC = "red") + stored.amount = length + stored.item_color = colorC + stored.update_icon() + +//////////////////////////////////////////// +// 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 + +///////////////////////////////////////////////// +// 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) + moveToNullspace() + powernet.remove_cable(src) //remove the cut cable from its powernet + + 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" + gender = NEUTER //That's a cable coil sounds better than that's some cable coils + icon = 'icons/obj/power.dmi' + icon_state = "coil" + item_state = "coil" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + max_amount = MAXCOIL + amount = MAXCOIL + merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves + item_color = "red" + desc = "A coil of insulated power cable." + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + materials = list(MAT_METAL=10, MAT_GLASS=5) + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + attack_verb = list("whipped", "lashed", "disciplined", "flogged") + singular_name = "cable piece" + full_w_class = WEIGHT_CLASS_SMALL + grind_results = list(/datum/reagent/copper = 2) //2 copper per cable in the coil + usesound = 'sound/items/deconstruct.ogg' + +/obj/item/stack/cable_coil/cyborg + is_cyborg = 1 + materials = list() + cost = 1 + +/obj/item/stack/cable_coil/cyborg/attack_self(mob/user) + var/cable_color = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") + item_color = cable_color + update_icon() + +/obj/item/stack/cable_coil/suicide_act(mob/user) + if(locate(/obj/structure/chair/stool) in get_turf(user)) + user.visible_message("[user] is making a noose with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + else + user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return(OXYLOSS) + +/obj/item/stack/cable_coil/Initialize(mapload, new_amount = null, param_color = null) + . = ..() + + var/list/cable_colors = GLOB.cable_colors + item_color = param_color || item_color || pick(cable_colors) + if(cable_colors[item_color]) + item_color = cable_colors[item_color] + + pixel_x = rand(-2,2) + pixel_y = rand(-2,2) + update_icon() + recipes = GLOB.cable_coil_recipes + +/////////////////////////////////// +// General procedures +/////////////////////////////////// + + +//you can use wires to heal robotics +/obj/item/stack/cable_coil/attack(mob/living/carbon/human/H, mob/user) + if(!istype(H)) + return ..() + + var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected)) + if(affecting && affecting.status == BODYPART_ROBOTIC) + if(user == H) + user.visible_message("[user] starts to fix some of the wires in [H]'s [affecting.name].", "You start fixing some of the wires in [H]'s [affecting.name].") + if(!do_mob(user, H, 50)) + return + if(item_heal_robotic(H, user, 0, 15)) + use(1) + return + else + return ..() + + +/obj/item/stack/cable_coil/update_icon() + icon_state = "[initial(item_state)][amount < 3 ? amount : ""]" + name = "cable [amount < 3 ? "piece" : "coil"]" + color = null + add_atom_colour(item_color, FIXED_COLOUR_PRIORITY) + +/obj/item/stack/cable_coil/attack_hand(mob/user) + . = ..() + if(.) + return + var/obj/item/stack/cable_coil/new_cable = ..() + if(istype(new_cable)) + new_cable.item_color = item_color + new_cable.update_icon() + +//add cables to the stack +/obj/item/stack/cable_coil/proc/give(extra) + if(amount + extra > max_amount) + amount = max_amount + else + amount += extra + update_icon() + + + +/////////////////////////////////////////////// +// Cable laying procedures +////////////////////////////////////////////// + +/obj/item/stack/cable_coil/proc/get_new_cable(location) + var/path = /obj/structure/cable + return new path(location, item_color) + +// called when cable_coil is clicked on a turf +/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.update_icon() + + //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, 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, showerror = TRUE, forceddir) + var/turf/U = user.loc + if(!isturf(U)) + return + + var/turf/T = C.loc + + 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 && !forceddir) //if clicked on the turf we're standing on and a direction wasn't supplied, 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 and no direction was supplied + if((C.d1 == dirn || C.d2 == dirn) && !forceddir) + if(!U.can_have_cabling()) //checking if it's a plating or catwalk + if (showerror) + to_chat(user, "You can only lay cables on catwalks and plating!") + return + if(U.intact) //can't place a cable if it's a plating with a tile on it + 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) + if (showerror) + 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 or we have a supplied direction, 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 + if (showerror) + to_chat(user, "There's already a cable at that position!") + + return + + + C.update_icon() + + C.d1 = nd1 + C.d2 = nd2 + + //updates the stored cable coil + C.update_stored(2, item_color) + + C.add_fingerprint(user) + C.update_icon() + + + 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/red + item_color = "red" + color = "#ff0000" + +/obj/item/stack/cable_coil/yellow + item_color = "yellow" + color = "#ffff00" + +/obj/item/stack/cable_coil/blue + item_color = "blue" + color = "#1919c8" + +/obj/item/stack/cable_coil/green + item_color = "green" + color = "#00aa00" + +/obj/item/stack/cable_coil/pink + item_color = "pink" + color = "#ff3ccd" + +/obj/item/stack/cable_coil/orange + item_color = "orange" + color = "#ff8000" + +/obj/item/stack/cable_coil/cyan + item_color = "cyan" + color = "#00ffff" + +/obj/item/stack/cable_coil/white + item_color = "white" + +/obj/item/stack/cable_coil/random + item_color = null + color = "#ffffff" + + +/obj/item/stack/cable_coil/random/five + amount = 5 + +/obj/item/stack/cable_coil/cut + amount = null + icon_state = "coil2" + +/obj/item/stack/cable_coil/cut/Initialize(mapload) + . = ..() + if(!amount) + amount = rand(1,2) + pixel_x = rand(-2,2) + pixel_y = rand(-2,2) + update_icon() + +/obj/item/stack/cable_coil/cut/red + item_color = "red" + color = "#ff0000" + +/obj/item/stack/cable_coil/cut/yellow + item_color = "yellow" + color = "#ffff00" + +/obj/item/stack/cable_coil/cut/blue + item_color = "blue" + color = "#1919c8" + +/obj/item/stack/cable_coil/cut/green + item_color = "green" + color = "#00aa00" + +/obj/item/stack/cable_coil/cut/pink + item_color = "pink" + color = "#ff3ccd" + +/obj/item/stack/cable_coil/cut/orange + item_color = "orange" + color = "#ff8000" + +/obj/item/stack/cable_coil/cut/cyan + item_color = "cyan" + color = "#00ffff" + +/obj/item/stack/cable_coil/cut/white + item_color = "white" + +/obj/item/stack/cable_coil/cut/random + item_color = null color = "#ffffff" \ No newline at end of file diff --git a/icons/effects/mapping_helpers.dmi b/icons/effects/mapping_helpers.dmi index dec83046b9112f1bcfbca17c0d6ac10a6e24ba62..e3cc3865b27fcc3771a19dabe0f40c2bba6a0e17 100644 GIT binary patch literal 6878 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 literal 5656 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 N-d$s@QVoaD{{gL08sGo` diff --git a/tgstation.dme b/tgstation.dme index 3819cc83..eeae69f2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1,3276 +1,3280 @@ - -// DM Environment file for tgstation.dme. -// All manual changes should be made outside the BEGIN_ and END_ blocks. -// New source code should be placed in .dm files: choose File/New --> Code File. -// BEGIN_INTERNALS -// END_INTERNALS - -// BEGIN_FILE_DIR -#define FILE_DIR . -// END_FILE_DIR - -// BEGIN_PREFERENCES -#define DEBUG -// END_PREFERENCES - -// BEGIN_INCLUDE -#include "_maps\_basemap.dm" -#include "code\_compile_options.dm" -#include "code\world.dm" -#include "code\__DEFINES\_globals.dm" -#include "code\__DEFINES\_protect.dm" -#include "code\__DEFINES\_tick.dm" -#include "code\__DEFINES\access.dm" -#include "code\__DEFINES\admin.dm" -#include "code\__DEFINES\antagonists.dm" -#include "code\__DEFINES\atmospherics.dm" -#include "code\__DEFINES\atom_hud.dm" -#include "code\__DEFINES\bsql.config.dm" -#include "code\__DEFINES\bsql.dm" -#include "code\__DEFINES\callbacks.dm" -#include "code\__DEFINES\cargo.dm" -#include "code\__DEFINES\cinematics.dm" -#include "code\__DEFINES\citadel_defines.dm" -#include "code\__DEFINES\cleaning.dm" -#include "code\__DEFINES\clockcult.dm" -#include "code\__DEFINES\colors.dm" -#include "code\__DEFINES\combat.dm" -#include "code\__DEFINES\components.dm" -#include "code\__DEFINES\configuration.dm" -#include "code\__DEFINES\construction.dm" -#include "code\__DEFINES\contracts.dm" -#include "code\__DEFINES\cult.dm" -#include "code\__DEFINES\diseases.dm" -#include "code\__DEFINES\DNA.dm" -#include "code\__DEFINES\events.dm" -#include "code\__DEFINES\exports.dm" -#include "code\__DEFINES\flags.dm" -#include "code\__DEFINES\food.dm" -#include "code\__DEFINES\footsteps.dm" -#include "code\__DEFINES\hud.dm" -#include "code\__DEFINES\integrated_electronics.dm" -#include "code\__DEFINES\interaction_flags.dm" -#include "code\__DEFINES\inventory.dm" -#include "code\__DEFINES\is_helpers.dm" -#include "code\__DEFINES\jobs.dm" -#include "code\__DEFINES\language.dm" -#include "code\__DEFINES\layers.dm" -#include "code\__DEFINES\lighting.dm" -#include "code\__DEFINES\logging.dm" -#include "code\__DEFINES\machines.dm" -#include "code\__DEFINES\maps.dm" -#include "code\__DEFINES\maths.dm" -#include "code\__DEFINES\MC.dm" -#include "code\__DEFINES\mecha.dm" -#include "code\__DEFINES\medal.dm" -#include "code\__DEFINES\melee.dm" -#include "code\__DEFINES\menu.dm" -#include "code\__DEFINES\misc.dm" -#include "code\__DEFINES\mobs.dm" -#include "code\__DEFINES\monkeys.dm" -#include "code\__DEFINES\move_force.dm" -#include "code\__DEFINES\movement.dm" -#include "code\__DEFINES\movespeed_modification.dm" -#include "code\__DEFINES\nanites.dm" -#include "code\__DEFINES\networks.dm" -#include "code\__DEFINES\obj_flags.dm" -#include "code\__DEFINES\pinpointers.dm" -#include "code\__DEFINES\pipe_construction.dm" -#include "code\__DEFINES\pool.dm" -#include "code\__DEFINES\preferences.dm" -#include "code\__DEFINES\profile.dm" -#include "code\__DEFINES\qdel.dm" -#include "code\__DEFINES\radiation.dm" -#include "code\__DEFINES\radio.dm" -#include "code\__DEFINES\reactions.dm" -#include "code\__DEFINES\reagents.dm" -#include "code\__DEFINES\reagents_specific_heat.dm" -#include "code\__DEFINES\research.dm" -#include "code\__DEFINES\robots.dm" -#include "code\__DEFINES\role_preferences.dm" -#include "code\__DEFINES\rust_g.config.dm" -#include "code\__DEFINES\rust_g.dm" -#include "code\__DEFINES\say.dm" -#include "code\__DEFINES\shuttles.dm" -#include "code\__DEFINES\sight.dm" -#include "code\__DEFINES\sound.dm" -#include "code\__DEFINES\spaceman_dmm.dm" -#include "code\__DEFINES\stat.dm" -#include "code\__DEFINES\stat_tracking.dm" -#include "code\__DEFINES\status_effects.dm" -#include "code\__DEFINES\subsystems.dm" -#include "code\__DEFINES\tgs.config.dm" -#include "code\__DEFINES\tgs.dm" -#include "code\__DEFINES\tgui.dm" -#include "code\__DEFINES\time.dm" -#include "code\__DEFINES\tools.dm" -#include "code\__DEFINES\traits.dm" -#include "code\__DEFINES\turf_flags.dm" -#include "code\__DEFINES\typeids.dm" -#include "code\__DEFINES\vehicles.dm" -#include "code\__DEFINES\voreconstants.dm" -#include "code\__DEFINES\vv.dm" -#include "code\__DEFINES\wall_dents.dm" -#include "code\__DEFINES\wires.dm" -#include "code\__DEFINES\dcs\signals.dm" -#include "code\__HELPERS\_cit_helpers.dm" -#include "code\__HELPERS\_lists.dm" -#include "code\__HELPERS\_logging.dm" -#include "code\__HELPERS\_string_lists.dm" -#include "code\__HELPERS\areas.dm" -#include "code\__HELPERS\AStar.dm" -#include "code\__HELPERS\cmp.dm" -#include "code\__HELPERS\dates.dm" -#include "code\__HELPERS\dna.dm" -#include "code\__HELPERS\files.dm" -#include "code\__HELPERS\game.dm" -#include "code\__HELPERS\global_lists.dm" -#include "code\__HELPERS\heap.dm" -#include "code\__HELPERS\icon_smoothing.dm" -#include "code\__HELPERS\icons.dm" -#include "code\__HELPERS\level_traits.dm" -#include "code\__HELPERS\matrices.dm" -#include "code\__HELPERS\mobs.dm" -#include "code\__HELPERS\mouse_control.dm" -#include "code\__HELPERS\names.dm" -#include "code\__HELPERS\priority_announce.dm" -#include "code\__HELPERS\pronouns.dm" -#include "code\__HELPERS\qdel.dm" -#include "code\__HELPERS\radiation.dm" -#include "code\__HELPERS\radio.dm" -#include "code\__HELPERS\reagents.dm" -#include "code\__HELPERS\roundend.dm" -#include "code\__HELPERS\sanitize_values.dm" -#include "code\__HELPERS\shell.dm" -#include "code\__HELPERS\stat_tracking.dm" -#include "code\__HELPERS\text.dm" -#include "code\__HELPERS\text_vr.dm" -#include "code\__HELPERS\time.dm" -#include "code\__HELPERS\type2type.dm" -#include "code\__HELPERS\type2type_vr.dm" -#include "code\__HELPERS\typelists.dm" -#include "code\__HELPERS\unsorted.dm" -#include "code\__HELPERS\view.dm" -#include "code\__HELPERS\sorts\__main.dm" -#include "code\__HELPERS\sorts\InsertSort.dm" -#include "code\__HELPERS\sorts\MergeSort.dm" -#include "code\__HELPERS\sorts\TimSort.dm" -#include "code\_globalvars\bitfields.dm" -#include "code\_globalvars\configuration.dm" -#include "code\_globalvars\game_modes.dm" -#include "code\_globalvars\genetics.dm" -#include "code\_globalvars\logging.dm" -#include "code\_globalvars\misc.dm" -#include "code\_globalvars\regexes.dm" -#include "code\_globalvars\lists\flavor_misc.dm" -#include "code\_globalvars\lists\maintenance_loot.dm" -#include "code\_globalvars\lists\mapping.dm" -#include "code\_globalvars\lists\medals.dm" -#include "code\_globalvars\lists\mobs.dm" -#include "code\_globalvars\lists\names.dm" -#include "code\_globalvars\lists\objects.dm" -#include "code\_globalvars\lists\poll_ignore.dm" -#include "code\_globalvars\lists\typecache.dm" -#include "code\_globalvars\lists\vending.dm" -#include "code\_js\byjax.dm" -#include "code\_js\menus.dm" -#include "code\_onclick\adjacent.dm" -#include "code\_onclick\ai.dm" -#include "code\_onclick\click.dm" -#include "code\_onclick\cyborg.dm" -#include "code\_onclick\drag_drop.dm" -#include "code\_onclick\item_attack.dm" -#include "code\_onclick\observer.dm" -#include "code\_onclick\other_mobs.dm" -#include "code\_onclick\overmind.dm" -#include "code\_onclick\telekinesis.dm" -#include "code\_onclick\hud\_defines.dm" -#include "code\_onclick\hud\action_button.dm" -#include "code\_onclick\hud\ai.dm" -#include "code\_onclick\hud\alert.dm" -#include "code\_onclick\hud\alien.dm" -#include "code\_onclick\hud\alien_larva.dm" -#include "code\_onclick\hud\blob_overmind.dm" -#include "code\_onclick\hud\blobbernauthud.dm" -#include "code\_onclick\hud\constructs.dm" -#include "code\_onclick\hud\credits.dm" -#include "code\_onclick\hud\devil.dm" -#include "code\_onclick\hud\drones.dm" -#include "code\_onclick\hud\fullscreen.dm" -#include "code\_onclick\hud\generic_dextrous.dm" -#include "code\_onclick\hud\ghost.dm" -#include "code\_onclick\hud\guardian.dm" -#include "code\_onclick\hud\hud.dm" -#include "code\_onclick\hud\hud_cit.dm" -#include "code\_onclick\hud\human.dm" -#include "code\_onclick\hud\monkey.dm" -#include "code\_onclick\hud\movable_screen_objects.dm" -#include "code\_onclick\hud\parallax.dm" -#include "code\_onclick\hud\picture_in_picture.dm" -#include "code\_onclick\hud\plane_master.dm" -#include "code\_onclick\hud\radial.dm" -#include "code\_onclick\hud\radial_persistent.dm" -#include "code\_onclick\hud\revenanthud.dm" -#include "code\_onclick\hud\robot.dm" -#include "code\_onclick\hud\screen_objects.dm" -#include "code\_onclick\hud\swarmer.dm" -#include "code\controllers\admin.dm" -#include "code\controllers\configuration_citadel.dm" -#include "code\controllers\controller.dm" -#include "code\controllers\failsafe.dm" -#include "code\controllers\globals.dm" -#include "code\controllers\hooks.dm" -#include "code\controllers\master.dm" -#include "code\controllers\subsystem.dm" -#include "code\controllers\configuration\config_entry.dm" -#include "code\controllers\configuration\configuration.dm" -#include "code\controllers\configuration\entries\comms.dm" -#include "code\controllers\configuration\entries\dbconfig.dm" -#include "code\controllers\configuration\entries\game_options.dm" -#include "code\controllers\configuration\entries\general.dm" -#include "code\controllers\subsystem\acid.dm" -#include "code\controllers\subsystem\adjacent_air.dm" -#include "code\controllers\subsystem\air.dm" -#include "code\controllers\subsystem\air_turfs.dm" -#include "code\controllers\subsystem\assets.dm" -#include "code\controllers\subsystem\atoms.dm" -#include "code\controllers\subsystem\augury.dm" -#include "code\controllers\subsystem\autotransfer.dm" -#include "code\controllers\subsystem\blackbox.dm" -#include "code\controllers\subsystem\chat.dm" -#include "code\controllers\subsystem\communications.dm" -#include "code\controllers\subsystem\dbcore.dm" -#include "code\controllers\subsystem\dcs.dm" -#include "code\controllers\subsystem\disease.dm" -#include "code\controllers\subsystem\events.dm" -#include "code\controllers\subsystem\fire_burning.dm" -#include "code\controllers\subsystem\garbage.dm" -#include "code\controllers\subsystem\icon_smooth.dm" -#include "code\controllers\subsystem\idlenpcpool.dm" -#include "code\controllers\subsystem\input.dm" -#include "code\controllers\subsystem\ipintel.dm" -#include "code\controllers\subsystem\item_spawning.dm" -#include "code\controllers\subsystem\job.dm" -#include "code\controllers\subsystem\jukeboxes.dm" -#include "code\controllers\subsystem\language.dm" -#include "code\controllers\subsystem\lighting.dm" -#include "code\controllers\subsystem\machines.dm" -#include "code\controllers\subsystem\mapping.dm" -#include "code\controllers\subsystem\medals.dm" -#include "code\controllers\subsystem\minor_mapping.dm" -#include "code\controllers\subsystem\mobs.dm" -#include "code\controllers\subsystem\moods.dm" -#include "code\controllers\subsystem\nightshift.dm" -#include "code\controllers\subsystem\npcpool.dm" -#include "code\controllers\subsystem\overlays.dm" -#include "code\controllers\subsystem\pai.dm" -#include "code\controllers\subsystem\parallax.dm" -#include "code\controllers\subsystem\pathfinder.dm" -#include "code\controllers\subsystem\persistence.dm" -#include "code\controllers\subsystem\ping.dm" -#include "code\controllers\subsystem\radiation.dm" -#include "code\controllers\subsystem\radio.dm" -#include "code\controllers\subsystem\research.dm" -#include "code\controllers\subsystem\server_maint.dm" -#include "code\controllers\subsystem\shuttle.dm" -#include "code\controllers\subsystem\spacedrift.dm" -#include "code\controllers\subsystem\stickyban.dm" -#include "code\controllers\subsystem\sun.dm" -#include "code\controllers\subsystem\tgui.dm" -#include "code\controllers\subsystem\throwing.dm" -#include "code\controllers\subsystem\ticker.dm" -#include "code\controllers\subsystem\time_track.dm" -#include "code\controllers\subsystem\timer.dm" -#include "code\controllers\subsystem\title.dm" -#include "code\controllers\subsystem\traumas.dm" -#include "code\controllers\subsystem\vis_overlays.dm" -#include "code\controllers\subsystem\vore.dm" -#include "code\controllers\subsystem\vote.dm" -#include "code\controllers\subsystem\weather.dm" -#include "code\controllers\subsystem\processing\chemistry.dm" -#include "code\controllers\subsystem\processing\circuit.dm" -#include "code\controllers\subsystem\processing\fastprocess.dm" -#include "code\controllers\subsystem\processing\fields.dm" -#include "code\controllers\subsystem\processing\nanites.dm" -#include "code\controllers\subsystem\processing\networks.dm" -#include "code\controllers\subsystem\processing\obj.dm" -#include "code\controllers\subsystem\processing\processing.dm" -#include "code\controllers\subsystem\processing\projectiles.dm" -#include "code\controllers\subsystem\processing\quirks.dm" -#include "code\controllers\subsystem\processing\wet_floors.dm" -#include "code\datums\action.dm" -#include "code\datums\ai_laws.dm" -#include "code\datums\armor.dm" -#include "code\datums\beam.dm" -#include "code\datums\browser.dm" -#include "code\datums\callback.dm" -#include "code\datums\chatmessage.dm" -#include "code\datums\cinematic.dm" -#include "code\datums\dash_weapon.dm" -#include "code\datums\datacore.dm" -#include "code\datums\datum.dm" -#include "code\datums\datumvars.dm" -#include "code\datums\dna.dm" -#include "code\datums\dog_fashion.dm" -#include "code\datums\embedding_behavior.dm" -#include "code\datums\emotes.dm" -#include "code\datums\ert.dm" -#include "code\datums\explosion.dm" -#include "code\datums\forced_movement.dm" -#include "code\datums\holocall.dm" -#include "code\datums\http.dm" -#include "code\datums\hud.dm" -#include "code\datums\map_config.dm" -#include "code\datums\martial.dm" -#include "code\datums\mind.dm" -#include "code\datums\mutable_appearance.dm" -#include "code\datums\numbered_display.dm" -#include "code\datums\outfit.dm" -#include "code\datums\position_point_vector.dm" -#include "code\datums\profiling.dm" -#include "code\datums\progressbar.dm" -#include "code\datums\radiation_wave.dm" -#include "code\datums\recipe.dm" -#include "code\datums\ruins.dm" -#include "code\datums\saymode.dm" -#include "code\datums\shuttles.dm" -#include "code\datums\soullink.dm" -#include "code\datums\spawners_menu.dm" -#include "code\datums\verbs.dm" -#include "code\datums\weakrefs.dm" -#include "code\datums\world_topic.dm" -#include "code\datums\actions\beam_rifle.dm" -#include "code\datums\actions\ninja.dm" -#include "code\datums\brain_damage\brain_trauma.dm" -#include "code\datums\brain_damage\hypnosis.dm" -#include "code\datums\brain_damage\imaginary_friend.dm" -#include "code\datums\brain_damage\mild.dm" -#include "code\datums\brain_damage\phobia.dm" -#include "code\datums\brain_damage\severe.dm" -#include "code\datums\brain_damage\special.dm" -#include "code\datums\brain_damage\split_personality.dm" -#include "code\datums\components\_component.dm" -#include "code\datums\components\anti_magic.dm" -#include "code\datums\components\armor_plate.dm" -#include "code\datums\components\bouncy.dm" -#include "code\datums\components\butchering.dm" -#include "code\datums\components\caltrop.dm" -#include "code\datums\components\chasm.dm" -#include "code\datums\components\construction.dm" -#include "code\datums\components\decal.dm" -#include "code\datums\components\earprotection.dm" -#include "code\datums\components\edit_complainer.dm" -#include "code\datums\components\empprotection.dm" -#include "code\datums\components\footstep.dm" -#include "code\datums\components\forced_gravity.dm" -#include "code\datums\components\infective.dm" -#include "code\datums\components\jousting.dm" -#include "code\datums\components\knockoff.dm" -#include "code\datums\components\lockon_aiming.dm" -#include "code\datums\components\magnetic_catch.dm" -#include "code\datums\components\material_container.dm" -#include "code\datums\components\mirage_border.dm" -#include "code\datums\components\mood.dm" -#include "code\datums\components\nanites.dm" -#include "code\datums\components\ntnet_interface.dm" -#include "code\datums\components\orbiter.dm" -#include "code\datums\components\paintable.dm" -#include "code\datums\components\rad_insulation.dm" -#include "code\datums\components\radioactive.dm" -#include "code\datums\components\remote_materials.dm" -#include "code\datums\components\riding.dm" -#include "code\datums\components\rotation.dm" -#include "code\datums\components\signal_redirect.dm" -#include "code\datums\components\sizzle.dm" -#include "code\datums\components\slippery.dm" -#include "code\datums\components\spawner.dm" -#include "code\datums\components\spooky.dm" -#include "code\datums\components\squeak.dm" -#include "code\datums\components\stationloving.dm" -#include "code\datums\components\swarming.dm" -#include "code\datums\components\thermite.dm" -#include "code\datums\components\uplink.dm" -#include "code\datums\components\wearertargeting.dm" -#include "code\datums\components\wet_floor.dm" -#include "code\datums\components\storage\storage.dm" -#include "code\datums\components\storage\concrete\_concrete.dm" -#include "code\datums\components\storage\concrete\bag_of_holding.dm" -#include "code\datums\components\storage\concrete\bluespace.dm" -#include "code\datums\components\storage\concrete\emergency.dm" -#include "code\datums\components\storage\concrete\implant.dm" -#include "code\datums\components\storage\concrete\pockets.dm" -#include "code\datums\components\storage\concrete\rped.dm" -#include "code\datums\components\storage\concrete\special.dm" -#include "code\datums\components\storage\concrete\stack.dm" -#include "code\datums\diseases\_disease.dm" -#include "code\datums\diseases\_MobProcs.dm" -#include "code\datums\diseases\anxiety.dm" -#include "code\datums\diseases\appendicitis.dm" -#include "code\datums\diseases\beesease.dm" -#include "code\datums\diseases\brainrot.dm" -#include "code\datums\diseases\cold.dm" -#include "code\datums\diseases\cold9.dm" -#include "code\datums\diseases\dna_spread.dm" -#include "code\datums\diseases\fake_gbs.dm" -#include "code\datums\diseases\flu.dm" -#include "code\datums\diseases\fluspanish.dm" -#include "code\datums\diseases\gbs.dm" -#include "code\datums\diseases\heart_failure.dm" -#include "code\datums\diseases\magnitis.dm" -#include "code\datums\diseases\parrotpossession.dm" -#include "code\datums\diseases\pierrot_throat.dm" -#include "code\datums\diseases\retrovirus.dm" -#include "code\datums\diseases\rhumba_beat.dm" -#include "code\datums\diseases\transformation.dm" -#include "code\datums\diseases\tuberculosis.dm" -#include "code\datums\diseases\wizarditis.dm" -#include "code\datums\diseases\advance\advance.dm" -#include "code\datums\diseases\advance\presets.dm" -#include "code\datums\diseases\advance\symptoms\beard.dm" -#include "code\datums\diseases\advance\symptoms\choking.dm" -#include "code\datums\diseases\advance\symptoms\confusion.dm" -#include "code\datums\diseases\advance\symptoms\cough.dm" -#include "code\datums\diseases\advance\symptoms\deafness.dm" -#include "code\datums\diseases\advance\symptoms\dizzy.dm" -#include "code\datums\diseases\advance\symptoms\fever.dm" -#include "code\datums\diseases\advance\symptoms\fire.dm" -#include "code\datums\diseases\advance\symptoms\flesh_eating.dm" -#include "code\datums\diseases\advance\symptoms\hallucigen.dm" -#include "code\datums\diseases\advance\symptoms\headache.dm" -#include "code\datums\diseases\advance\symptoms\heal.dm" -#include "code\datums\diseases\advance\symptoms\itching.dm" -#include "code\datums\diseases\advance\symptoms\nanites.dm" -#include "code\datums\diseases\advance\symptoms\narcolepsy.dm" -#include "code\datums\diseases\advance\symptoms\oxygen.dm" -#include "code\datums\diseases\advance\symptoms\sensory.dm" -#include "code\datums\diseases\advance\symptoms\shedding.dm" -#include "code\datums\diseases\advance\symptoms\shivering.dm" -#include "code\datums\diseases\advance\symptoms\skin.dm" -#include "code\datums\diseases\advance\symptoms\sneeze.dm" -#include "code\datums\diseases\advance\symptoms\species.dm" -#include "code\datums\diseases\advance\symptoms\symptoms.dm" -#include "code\datums\diseases\advance\symptoms\viral.dm" -#include "code\datums\diseases\advance\symptoms\vision.dm" -#include "code\datums\diseases\advance\symptoms\voice_change.dm" -#include "code\datums\diseases\advance\symptoms\vomit.dm" -#include "code\datums\diseases\advance\symptoms\weight.dm" -#include "code\datums\diseases\advance\symptoms\youth.dm" -#include "code\datums\elements\_element.dm" -#include "code\datums\elements\cleaning.dm" -#include "code\datums\elements\earhealing.dm" -#include "code\datums\elements\ghost_role_eligibility.dm" -#include "code\datums\elements\mob_holder.dm" -#include "code\datums\elements\swimming.dm" -#include "code\datums\elements\wuv.dm" -#include "code\datums\helper_datums\events.dm" -#include "code\datums\helper_datums\getrev.dm" -#include "code\datums\helper_datums\icon_snapshot.dm" -#include "code\datums\helper_datums\teleport.dm" -#include "code\datums\helper_datums\topic_input.dm" -#include "code\datums\looping_sounds\_looping_sound.dm" -#include "code\datums\looping_sounds\item_sounds.dm" -#include "code\datums\looping_sounds\machinery_sounds.dm" -#include "code\datums\looping_sounds\weather.dm" -#include "code\datums\martial\boxing.dm" -#include "code\datums\martial\cqc.dm" -#include "code\datums\martial\krav_maga.dm" -#include "code\datums\martial\mushpunch.dm" -#include "code\datums\martial\plasma_fist.dm" -#include "code\datums\martial\psychotic_brawl.dm" -#include "code\datums\martial\sleeping_carp.dm" -#include "code\datums\martial\wrestling.dm" -#include "code\datums\mood_events\beauty_events.dm" -#include "code\datums\mood_events\drink_events.dm" -#include "code\datums\mood_events\drug_events.dm" -#include "code\datums\mood_events\generic_negative_events.dm" -#include "code\datums\mood_events\generic_positive_events.dm" -#include "code\datums\mood_events\mood_event.dm" -#include "code\datums\mood_events\needs_events.dm" -#include "code\datums\mutations\_mutations.dm" -#include "code\datums\mutations\actions.dm" -#include "code\datums\mutations\antenna.dm" -#include "code\datums\mutations\body.dm" -#include "code\datums\mutations\chameleon.dm" -#include "code\datums\mutations\cold_resistance.dm" -#include "code\datums\mutations\combined.dm" -#include "code\datums\mutations\hulk.dm" -#include "code\datums\mutations\radioactive.dm" -#include "code\datums\mutations\sight.dm" -#include "code\datums\mutations\speech.dm" -#include "code\datums\mutations\telekinesis.dm" -#include "code\datums\ruins\lavaland.dm" -#include "code\datums\ruins\space.dm" -#include "code\datums\ruins\station.dm" -#include "code\datums\status_effects\buffs.dm" -#include "code\datums\status_effects\debuffs.dm" -#include "code\datums\status_effects\gas.dm" -#include "code\datums\status_effects\neutral.dm" -#include "code\datums\status_effects\status_effect.dm" -#include "code\datums\traits\_quirk.dm" -#include "code\datums\traits\good.dm" -#include "code\datums\traits\negative.dm" -#include "code\datums\traits\neutral.dm" -#include "code\datums\weather\weather.dm" -#include "code\datums\weather\weather_types\acid_rain.dm" -#include "code\datums\weather\weather_types\ash_storm.dm" -#include "code\datums\weather\weather_types\floor_is_lava.dm" -#include "code\datums\weather\weather_types\radiation_storm.dm" -#include "code\datums\weather\weather_types\snow_storm.dm" -#include "code\datums\wires\_wires.dm" -#include "code\datums\wires\airalarm.dm" -#include "code\datums\wires\airlock.dm" -#include "code\datums\wires\apc.dm" -#include "code\datums\wires\autolathe.dm" -#include "code\datums\wires\emitter.dm" -#include "code\datums\wires\explosive.dm" -#include "code\datums\wires\microwave.dm" -#include "code\datums\wires\mulebot.dm" -#include "code\datums\wires\particle_accelerator.dm" -#include "code\datums\wires\r_n_d.dm" -#include "code\datums\wires\radio.dm" -#include "code\datums\wires\robot.dm" -#include "code\datums\wires\suit_storage_unit.dm" -#include "code\datums\wires\syndicatebomb.dm" -#include "code\datums\wires\tesla_coil.dm" -#include "code\datums\wires\vending.dm" -#include "code\game\alternate_appearance.dm" -#include "code\game\atoms.dm" -#include "code\game\atoms_movable.dm" -#include "code\game\communications.dm" -#include "code\game\data_huds.dm" -#include "code\game\say.dm" -#include "code\game\shuttle_engines.dm" -#include "code\game\sound.dm" -#include "code\game\world.dm" -#include "code\game\area\ai_monitored.dm" -#include "code\game\area\areas.dm" -#include "code\game\area\Space_Station_13_areas.dm" -#include "code\game\area\areas\away_content.dm" -#include "code\game\area\areas\centcom.dm" -#include "code\game\area\areas\holodeck.dm" -#include "code\game\area\areas\mining.dm" -#include "code\game\area\areas\shuttles.dm" -#include "code\game\area\areas\ruins\_ruins.dm" -#include "code\game\area\areas\ruins\lavaland.dm" -#include "code\game\area\areas\ruins\space.dm" -#include "code\game\area\areas\ruins\templates.dm" -#include "code\game\gamemodes\events.dm" -#include "code\game\gamemodes\game_mode.dm" -#include "code\game\gamemodes\objective.dm" -#include "code\game\gamemodes\objective_items.dm" -#include "code\game\gamemodes\bloodsucker\bloodsucker.dm" -#include "code\game\gamemodes\bloodsucker\hunter.dm" -#include "code\game\gamemodes\brother\traitor_bro.dm" -#include "code\game\gamemodes\changeling\changeling.dm" -#include "code\game\gamemodes\changeling\traitor_chan.dm" -#include "code\game\gamemodes\clock_cult\clock_cult.dm" -#include "code\game\gamemodes\clown_ops\bananium_bomb.dm" -#include "code\game\gamemodes\clown_ops\clown_ops.dm" -#include "code\game\gamemodes\clown_ops\clown_weapons.dm" -#include "code\game\gamemodes\cult\cult.dm" -#include "code\game\gamemodes\devil\devil_game_mode.dm" -#include "code\game\gamemodes\devil\game_mode.dm" -#include "code\game\gamemodes\devil\objectives.dm" -#include "code\game\gamemodes\devil\devil agent\devil_agent.dm" -#include "code\game\gamemodes\dynamic\dynamic.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets_events.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm" -#include "code\game\gamemodes\extended\extended.dm" -#include "code\game\gamemodes\meteor\meteor.dm" -#include "code\game\gamemodes\meteor\meteors.dm" -#include "code\game\gamemodes\monkey\monkey.dm" -#include "code\game\gamemodes\nuclear\nuclear.dm" -#include "code\game\gamemodes\overthrow\objective.dm" -#include "code\game\gamemodes\overthrow\overthrow.dm" -#include "code\game\gamemodes\revolution\revolution.dm" -#include "code\game\gamemodes\sandbox\airlock_maker.dm" -#include "code\game\gamemodes\sandbox\h_sandbox.dm" -#include "code\game\gamemodes\sandbox\sandbox.dm" -#include "code\game\gamemodes\traitor\double_agents.dm" -#include "code\game\gamemodes\traitor\traitor.dm" -#include "code\game\gamemodes\wizard\wizard.dm" -#include "code\game\machinery\_machinery.dm" -#include "code\game\machinery\ai_slipper.dm" -#include "code\game\machinery\airlock_control.dm" -#include "code\game\machinery\announcement_system.dm" -#include "code\game\machinery\aug_manipulator.dm" -#include "code\game\machinery\autolathe.dm" -#include "code\game\machinery\bank_machine.dm" -#include "code\game\machinery\Beacon.dm" -#include "code\game\machinery\bloodbankgen.dm" -#include "code\game\machinery\buttons.dm" -#include "code\game\machinery\cell_charger.dm" -#include "code\game\machinery\cloning.dm" -#include "code\game\machinery\constructable_frame.dm" -#include "code\game\machinery\dance_machine.dm" -#include "code\game\machinery\defibrillator_mount.dm" -#include "code\game\machinery\deployable.dm" -#include "code\game\machinery\dish_drive.dm" -#include "code\game\machinery\dna_scanner.dm" -#include "code\game\machinery\doppler_array.dm" -#include "code\game\machinery\droneDispenser.dm" -#include "code\game\machinery\exp_cloner.dm" -#include "code\game\machinery\firealarm.dm" -#include "code\game\machinery\flasher.dm" -#include "code\game\machinery\gulag_item_reclaimer.dm" -#include "code\game\machinery\gulag_teleporter.dm" -#include "code\game\machinery\harvester.dm" -#include "code\game\machinery\hologram.dm" -#include "code\game\machinery\igniter.dm" -#include "code\game\machinery\iv_drip.dm" -#include "code\game\machinery\launch_pad.dm" -#include "code\game\machinery\lightswitch.dm" -#include "code\game\machinery\limbgrower.dm" -#include "code\game\machinery\magnet.dm" -#include "code\game\machinery\mass_driver.dm" -#include "code\game\machinery\navbeacon.dm" -#include "code\game\machinery\newscaster.dm" -#include "code\game\machinery\PDApainter.dm" -#include "code\game\machinery\posi_alert.dm" -#include "code\game\machinery\quantum_pad.dm" -#include "code\game\machinery\recharger.dm" -#include "code\game\machinery\rechargestation.dm" -#include "code\game\machinery\recycler.dm" -#include "code\game\machinery\requests_console.dm" -#include "code\game\machinery\shieldgen.dm" -#include "code\game\machinery\Sleeper.dm" -#include "code\game\machinery\slotmachine.dm" -#include "code\game\machinery\spaceheater.dm" -#include "code\game\machinery\stasis.dm" -#include "code\game\machinery\status_display.dm" -#include "code\game\machinery\suit_storage_unit.dm" -#include "code\game\machinery\syndicatebeacon.dm" -#include "code\game\machinery\syndicatebomb.dm" -#include "code\game\machinery\teleporter.dm" -#include "code\game\machinery\transformer.dm" -#include "code\game\machinery\washing_machine.dm" -#include "code\game\machinery\wishgranter.dm" -#include "code\game\machinery\camera\camera.dm" -#include "code\game\machinery\camera\camera_assembly.dm" -#include "code\game\machinery\camera\motion.dm" -#include "code\game\machinery\camera\presets.dm" -#include "code\game\machinery\camera\tracking.dm" -#include "code\game\machinery\computer\_computer.dm" -#include "code\game\machinery\computer\aifixer.dm" -#include "code\game\machinery\computer\apc_control.dm" -#include "code\game\machinery\computer\arcade.dm" -#include "code\game\machinery\computer\atmos_alert.dm" -#include "code\game\machinery\computer\atmos_control.dm" -#include "code\game\machinery\computer\buildandrepair.dm" -#include "code\game\machinery\computer\camera.dm" -#include "code\game\machinery\computer\camera_advanced.dm" -#include "code\game\machinery\computer\card.dm" -#include "code\game\machinery\computer\cloning.dm" -#include "code\game\machinery\computer\communications.dm" -#include "code\game\machinery\computer\crew.dm" -#include "code\game\machinery\computer\dna_console.dm" -#include "code\game\machinery\computer\launchpad_control.dm" -#include "code\game\machinery\computer\law.dm" -#include "code\game\machinery\computer\medical.dm" -#include "code\game\machinery\computer\Operating.dm" -#include "code\game\machinery\computer\pod.dm" -#include "code\game\machinery\computer\robot.dm" -#include "code\game\machinery\computer\security.dm" -#include "code\game\machinery\computer\station_alert.dm" -#include "code\game\machinery\computer\telecrystalconsoles.dm" -#include "code\game\machinery\computer\teleporter.dm" -#include "code\game\machinery\computer\arcade\battle.dm" -#include "code\game\machinery\computer\arcade\minesweeper.dm" -#include "code\game\machinery\computer\arcade\misc_arcade.dm" -#include "code\game\machinery\computer\arcade\orion_trail.dm" -#include "code\game\machinery\computer\prisoner\_prisoner.dm" -#include "code\game\machinery\computer\prisoner\gulag_teleporter.dm" -#include "code\game\machinery\computer\prisoner\management.dm" -#include "code\game\machinery\doors\airlock.dm" -#include "code\game\machinery\doors\airlock_electronics.dm" -#include "code\game\machinery\doors\airlock_types.dm" -#include "code\game\machinery\doors\alarmlock.dm" -#include "code\game\machinery\doors\brigdoors.dm" -#include "code\game\machinery\doors\checkForMultipleDoors.dm" -#include "code\game\machinery\doors\door.dm" -#include "code\game\machinery\doors\firedoor.dm" -#include "code\game\machinery\doors\passworddoor.dm" -#include "code\game\machinery\doors\poddoor.dm" -#include "code\game\machinery\doors\shutters.dm" -#include "code\game\machinery\doors\unpowered.dm" -#include "code\game\machinery\doors\windowdoor.dm" -#include "code\game\machinery\embedded_controller\access_controller.dm" -#include "code\game\machinery\embedded_controller\airlock_controller.dm" -#include "code\game\machinery\embedded_controller\embedded_controller_base.dm" -#include "code\game\machinery\embedded_controller\simple_vent_controller.dm" -#include "code\game\machinery\pipe\construction.dm" -#include "code\game\machinery\pipe\pipe_dispenser.dm" -#include "code\game\machinery\porta_turret\portable_turret.dm" -#include "code\game\machinery\porta_turret\portable_turret_construct.dm" -#include "code\game\machinery\porta_turret\portable_turret_cover.dm" -#include "code\game\machinery\poweredfans\fan_assembly.dm" -#include "code\game\machinery\poweredfans\poweredfans.dm" -#include "code\game\machinery\telecomms\broadcasting.dm" -#include "code\game\machinery\telecomms\machine_interactions.dm" -#include "code\game\machinery\telecomms\telecomunications.dm" -#include "code\game\machinery\telecomms\computers\logbrowser.dm" -#include "code\game\machinery\telecomms\computers\message.dm" -#include "code\game\machinery\telecomms\computers\telemonitor.dm" -#include "code\game\machinery\telecomms\machines\allinone.dm" -#include "code\game\machinery\telecomms\machines\broadcaster.dm" -#include "code\game\machinery\telecomms\machines\bus.dm" -#include "code\game\machinery\telecomms\machines\hub.dm" -#include "code\game\machinery\telecomms\machines\message_server.dm" -#include "code\game\machinery\telecomms\machines\processor.dm" -#include "code\game\machinery\telecomms\machines\receiver.dm" -#include "code\game\machinery\telecomms\machines\relay.dm" -#include "code\game\machinery\telecomms\machines\server.dm" -#include "code\game\mecha\mech_bay.dm" -#include "code\game\mecha\mech_fabricator.dm" -#include "code\game\mecha\mecha.dm" -#include "code\game\mecha\mecha_actions.dm" -#include "code\game\mecha\mecha_construction_paths.dm" -#include "code\game\mecha\mecha_control_console.dm" -#include "code\game\mecha\mecha_defense.dm" -#include "code\game\mecha\mecha_parts.dm" -#include "code\game\mecha\mecha_topic.dm" -#include "code\game\mecha\mecha_wreckage.dm" -#include "code\game\mecha\combat\combat.dm" -#include "code\game\mecha\combat\durand.dm" -#include "code\game\mecha\combat\gygax.dm" -#include "code\game\mecha\combat\honker.dm" -#include "code\game\mecha\combat\marauder.dm" -#include "code\game\mecha\combat\neovgre.dm" -#include "code\game\mecha\combat\phazon.dm" -#include "code\game\mecha\combat\reticence.dm" -#include "code\game\mecha\equipment\mecha_equipment.dm" -#include "code\game\mecha\equipment\tools\medical_tools.dm" -#include "code\game\mecha\equipment\tools\mining_tools.dm" -#include "code\game\mecha\equipment\tools\other_tools.dm" -#include "code\game\mecha\equipment\tools\work_tools.dm" -#include "code\game\mecha\equipment\weapons\weapons.dm" -#include "code\game\mecha\medical\medical.dm" -#include "code\game\mecha\medical\odysseus.dm" -#include "code\game\mecha\working\ripley.dm" -#include "code\game\mecha\working\working.dm" -#include "code\game\objects\buckling.dm" -#include "code\game\objects\empulse.dm" -#include "code\game\objects\items.dm" -#include "code\game\objects\obj_defense.dm" -#include "code\game\objects\objs.dm" -#include "code\game\objects\structures.dm" -#include "code\game\objects\effects\alien_acid.dm" -#include "code\game\objects\effects\anomalies.dm" -#include "code\game\objects\effects\blessing.dm" -#include "code\game\objects\effects\bump_teleporter.dm" -#include "code\game\objects\effects\contraband.dm" -#include "code\game\objects\effects\countdown.dm" -#include "code\game\objects\effects\effects.dm" -#include "code\game\objects\effects\forcefields.dm" -#include "code\game\objects\effects\glowshroom.dm" -#include "code\game\objects\effects\landmarks.dm" -#include "code\game\objects\effects\mines.dm" -#include "code\game\objects\effects\misc.dm" -#include "code\game\objects\effects\overlays.dm" -#include "code\game\objects\effects\portals.dm" -#include "code\game\objects\effects\proximity.dm" -#include "code\game\objects\effects\spiders.dm" -#include "code\game\objects\effects\step_triggers.dm" -#include "code\game\objects\effects\wanted_poster.dm" -#include "code\game\objects\effects\decals\cleanable.dm" -#include "code\game\objects\effects\decals\crayon.dm" -#include "code\game\objects\effects\decals\decal.dm" -#include "code\game\objects\effects\decals\misc.dm" -#include "code\game\objects\effects\decals\remains.dm" -#include "code\game\objects\effects\decals\cleanable\aliens.dm" -#include "code\game\objects\effects\decals\cleanable\gibs.dm" -#include "code\game\objects\effects\decals\cleanable\humans.dm" -#include "code\game\objects\effects\decals\cleanable\misc.dm" -#include "code\game\objects\effects\decals\cleanable\robots.dm" -#include "code\game\objects\effects\decals\turfdecal\dirt.dm" -#include "code\game\objects\effects\decals\turfdecal\markings.dm" -#include "code\game\objects\effects\decals\turfdecal\tilecoloring.dm" -#include "code\game\objects\effects\decals\turfdecal\weather.dm" -#include "code\game\objects\effects\effect_system\effect_system.dm" -#include "code\game\objects\effects\effect_system\effects_explosion.dm" -#include "code\game\objects\effects\effect_system\effects_foam.dm" -#include "code\game\objects\effects\effect_system\effects_other.dm" -#include "code\game\objects\effects\effect_system\effects_smoke.dm" -#include "code\game\objects\effects\effect_system\effects_sparks.dm" -#include "code\game\objects\effects\effect_system\effects_water.dm" -#include "code\game\objects\effects\spawners\bombspawner.dm" -#include "code\game\objects\effects\spawners\bundle.dm" -#include "code\game\objects\effects\spawners\gibspawner.dm" -#include "code\game\objects\effects\spawners\lootdrop.dm" -#include "code\game\objects\effects\spawners\structure.dm" -#include "code\game\objects\effects\spawners\traps.dm" -#include "code\game\objects\effects\spawners\vaultspawner.dm" -#include "code\game\objects\effects\spawners\xeno_egg_delivery.dm" -#include "code\game\objects\effects\temporary_visuals\clockcult.dm" -#include "code\game\objects\effects\temporary_visuals\cult.dm" -#include "code\game\objects\effects\temporary_visuals\miscellaneous.dm" -#include "code\game\objects\effects\temporary_visuals\temporary_visual.dm" -#include "code\game\objects\effects\temporary_visuals\projectiles\impact.dm" -#include "code\game\objects\effects\temporary_visuals\projectiles\muzzle.dm" -#include "code\game\objects\effects\temporary_visuals\projectiles\projectile_effects.dm" -#include "code\game\objects\effects\temporary_visuals\projectiles\tracer.dm" -#include "code\game\objects\items\AI_modules.dm" -#include "code\game\objects\items\airlock_painter.dm" -#include "code\game\objects\items\apc_frame.dm" -#include "code\game\objects\items\blueprints.dm" -#include "code\game\objects\items\body_egg.dm" -#include "code\game\objects\items\bodybag.dm" -#include "code\game\objects\items\candle.dm" -#include "code\game\objects\items\cardboard_cutouts.dm" -#include "code\game\objects\items\cards_ids.dm" -#include "code\game\objects\items\charter.dm" -#include "code\game\objects\items\chromosome.dm" -#include "code\game\objects\items\chrono_eraser.dm" -#include "code\game\objects\items\cigs_lighters.dm" -#include "code\game\objects\items\clown_items.dm" -#include "code\game\objects\items\control_wand.dm" -#include "code\game\objects\items\cosmetics.dm" -#include "code\game\objects\items\courtroom.dm" -#include "code\game\objects\items\crayons.dm" -#include "code\game\objects\items\defib.dm" -#include "code\game\objects\items\dehy_carp.dm" -#include "code\game\objects\items\dice.dm" -#include "code\game\objects\items\dna_injector.dm" -#include "code\game\objects\items\documents.dm" -#include "code\game\objects\items\eightball.dm" -#include "code\game\objects\items\extinguisher.dm" -#include "code\game\objects\items\flamethrower.dm" -#include "code\game\objects\items\gift.dm" -#include "code\game\objects\items\granters.dm" -#include "code\game\objects\items\handcuffs.dm" -#include "code\game\objects\items\his_grace.dm" -#include "code\game\objects\items\holosign_creator.dm" -#include "code\game\objects\items\holy_weapons.dm" -#include "code\game\objects\items\hot_potato.dm" -#include "code\game\objects\items\inducer.dm" -#include "code\game\objects\items\kitchen.dm" -#include "code\game\objects\items\latexballoon.dm" -#include "code\game\objects\items\lorebooks.dm" -#include "code\game\objects\items\manuals.dm" -#include "code\game\objects\items\mesmetron.dm" -#include "code\game\objects\items\miscellaneous.dm" -#include "code\game\objects\items\mop.dm" -#include "code\game\objects\items\paint.dm" -#include "code\game\objects\items\paiwire.dm" -#include "code\game\objects\items\pet_carrier.dm" -#include "code\game\objects\items\pinpointer.dm" -#include "code\game\objects\items\plushes.dm" -#include "code\game\objects\items\pneumaticCannon.dm" -#include "code\game\objects\items\powerfist.dm" -#include "code\game\objects\items\RCD.dm" -#include "code\game\objects\items\RCL.dm" -#include "code\game\objects\items\religion.dm" -#include "code\game\objects\items\RPD.dm" -#include "code\game\objects\items\RSF.dm" -#include "code\game\objects\items\scrolls.dm" -#include "code\game\objects\items\sharpener.dm" -#include "code\game\objects\items\shields.dm" -#include "code\game\objects\items\shooting_range.dm" -#include "code\game\objects\items\signs.dm" -#include "code\game\objects\items\singularityhammer.dm" -#include "code\game\objects\items\stunbaton.dm" -#include "code\game\objects\items\taster.dm" -#include "code\game\objects\items\teleportation.dm" -#include "code\game\objects\items\teleprod.dm" -#include "code\game\objects\items\theft_tools.dm" -#include "code\game\objects\items\toys.dm" -#include "code\game\objects\items\trash.dm" -#include "code\game\objects\items\twohanded.dm" -#include "code\game\objects\items\vending_items.dm" -#include "code\game\objects\items\weaponry.dm" -#include "code\game\objects\items\circuitboards\circuitboard.dm" -#include "code\game\objects\items\circuitboards\computer_circuitboards.dm" -#include "code\game\objects\items\circuitboards\machine_circuitboards.dm" -#include "code\game\objects\items\devices\aicard.dm" -#include "code\game\objects\items\devices\anomaly_neutralizer.dm" -#include "code\game\objects\items\devices\beacon.dm" -#include "code\game\objects\items\devices\camera_bug.dm" -#include "code\game\objects\items\devices\chameleonproj.dm" -#include "code\game\objects\items\devices\compressionkit.dm" -#include "code\game\objects\items\devices\desynchronizer.dm" -#include "code\game\objects\items\devices\dogborg_sleeper.dm" -#include "code\game\objects\items\devices\doorCharge.dm" -#include "code\game\objects\items\devices\electroadaptive_pseudocircuit.dm" -#include "code\game\objects\items\devices\flashlight.dm" -#include "code\game\objects\items\devices\forcefieldprojector.dm" -#include "code\game\objects\items\devices\geiger_counter.dm" -#include "code\game\objects\items\devices\glue.dm" -#include "code\game\objects\items\devices\gps.dm" -#include "code\game\objects\items\devices\instruments.dm" -#include "code\game\objects\items\devices\laserpointer.dm" -#include "code\game\objects\items\devices\lightreplacer.dm" -#include "code\game\objects\items\devices\megaphone.dm" -#include "code\game\objects\items\devices\multitool.dm" -#include "code\game\objects\items\devices\paicard.dm" -#include "code\game\objects\items\devices\pipe_painter.dm" -#include "code\game\objects\items\devices\powersink.dm" -#include "code\game\objects\items\devices\pressureplates.dm" -#include "code\game\objects\items\devices\quantum_keycard.dm" -#include "code\game\objects\items\devices\reverse_bear_trap.dm" -#include "code\game\objects\items\devices\scanners.dm" -#include "code\game\objects\items\devices\sensor_device.dm" -#include "code\game\objects\items\devices\taperecorder.dm" -#include "code\game\objects\items\devices\traitordevices.dm" -#include "code\game\objects\items\devices\transfer_valve.dm" -#include "code\game\objects\items\devices\PDA\cart.dm" -#include "code\game\objects\items\devices\PDA\PDA.dm" -#include "code\game\objects\items\devices\PDA\PDA_types.dm" -#include "code\game\objects\items\devices\PDA\radio.dm" -#include "code\game\objects\items\devices\PDA\virus_cart.dm" -#include "code\game\objects\items\devices\radio\electropack.dm" -#include "code\game\objects\items\devices\radio\encryptionkey.dm" -#include "code\game\objects\items\devices\radio\headset.dm" -#include "code\game\objects\items\devices\radio\intercom.dm" -#include "code\game\objects\items\devices\radio\radio.dm" -#include "code\game\objects\items\grenades\antigravity.dm" -#include "code\game\objects\items\grenades\chem_grenade.dm" -#include "code\game\objects\items\grenades\clusterbuster.dm" -#include "code\game\objects\items\grenades\emgrenade.dm" -#include "code\game\objects\items\grenades\flashbang.dm" -#include "code\game\objects\items\grenades\ghettobomb.dm" -#include "code\game\objects\items\grenades\grenade.dm" -#include "code\game\objects\items\grenades\plastic.dm" -#include "code\game\objects\items\grenades\smokebomb.dm" -#include "code\game\objects\items\grenades\spawnergrenade.dm" -#include "code\game\objects\items\grenades\syndieminibomb.dm" -#include "code\game\objects\items\implants\implant.dm" -#include "code\game\objects\items\implants\implant_abductor.dm" -#include "code\game\objects\items\implants\implant_chem.dm" -#include "code\game\objects\items\implants\implant_clown.dm" -#include "code\game\objects\items\implants\implant_exile.dm" -#include "code\game\objects\items\implants\implant_explosive.dm" -#include "code\game\objects\items\implants\implant_freedom.dm" -#include "code\game\objects\items\implants\implant_krav_maga.dm" -#include "code\game\objects\items\implants\implant_mindshield.dm" -#include "code\game\objects\items\implants\implant_misc.dm" -#include "code\game\objects\items\implants\implant_radio.dm" -#include "code\game\objects\items\implants\implant_slave.dm" -#include "code\game\objects\items\implants\implant_spell.dm" -#include "code\game\objects\items\implants\implant_stealth.dm" -#include "code\game\objects\items\implants\implant_storage.dm" -#include "code\game\objects\items\implants\implant_track.dm" -#include "code\game\objects\items\implants\implant_uplink.dm" -#include "code\game\objects\items\implants\implantcase.dm" -#include "code\game\objects\items\implants\implantchair.dm" -#include "code\game\objects\items\implants\implanter.dm" -#include "code\game\objects\items\implants\implantpad.dm" -#include "code\game\objects\items\melee\energy.dm" -#include "code\game\objects\items\melee\misc.dm" -#include "code\game\objects\items\melee\transforming.dm" -#include "code\game\objects\items\robot\ai_upgrades.dm" -#include "code\game\objects\items\robot\robot_items.dm" -#include "code\game\objects\items\robot\robot_parts.dm" -#include "code\game\objects\items\robot\robot_upgrades.dm" -#include "code\game\objects\items\stacks\bscrystal.dm" -#include "code\game\objects\items\stacks\cash.dm" -#include "code\game\objects\items\stacks\medical.dm" -#include "code\game\objects\items\stacks\rods.dm" -#include "code\game\objects\items\stacks\stack.dm" -#include "code\game\objects\items\stacks\telecrystal.dm" -#include "code\game\objects\items\stacks\wrap.dm" -#include "code\game\objects\items\stacks\sheets\glass.dm" -#include "code\game\objects\items\stacks\sheets\leather.dm" -#include "code\game\objects\items\stacks\sheets\light.dm" -#include "code\game\objects\items\stacks\sheets\mineral.dm" -#include "code\game\objects\items\stacks\sheets\sheet_types.dm" -#include "code\game\objects\items\stacks\sheets\sheets.dm" -#include "code\game\objects\items\stacks\tiles\light.dm" -#include "code\game\objects\items\stacks\tiles\tile_mineral.dm" -#include "code\game\objects\items\stacks\tiles\tile_reskinning.dm" -#include "code\game\objects\items\stacks\tiles\tile_types.dm" -#include "code\game\objects\items\storage\backpack.dm" -#include "code\game\objects\items\storage\bags.dm" -#include "code\game\objects\items\storage\belt.dm" -#include "code\game\objects\items\storage\book.dm" -#include "code\game\objects\items\storage\boxes.dm" -#include "code\game\objects\items\storage\briefcase.dm" -#include "code\game\objects\items\storage\dakis.dm" -#include "code\game\objects\items\storage\fancy.dm" -#include "code\game\objects\items\storage\firstaid.dm" -#include "code\game\objects\items\storage\lockbox.dm" -#include "code\game\objects\items\storage\secure.dm" -#include "code\game\objects\items\storage\storage.dm" -#include "code\game\objects\items\storage\toolbox.dm" -#include "code\game\objects\items\storage\uplink_kits.dm" -#include "code\game\objects\items\storage\wallets.dm" -#include "code\game\objects\items\tanks\jetpack.dm" -#include "code\game\objects\items\tanks\tank_types.dm" -#include "code\game\objects\items\tanks\tanks.dm" -#include "code\game\objects\items\tanks\watertank.dm" -#include "code\game\objects\items\tools\crowbar.dm" -#include "code\game\objects\items\tools\screwdriver.dm" -#include "code\game\objects\items\tools\weldingtool.dm" -#include "code\game\objects\items\tools\wirecutters.dm" -#include "code\game\objects\items\tools\wrench.dm" -#include "code\game\objects\structures\ai_core.dm" -#include "code\game\objects\structures\aliens.dm" -#include "code\game\objects\structures\artstuff.dm" -#include "code\game\objects\structures\barsigns.dm" -#include "code\game\objects\structures\bedsheet_bin.dm" -#include "code\game\objects\structures\destructible_structures.dm" -#include "code\game\objects\structures\displaycase.dm" -#include "code\game\objects\structures\divine.dm" -#include "code\game\objects\structures\door_assembly.dm" -#include "code\game\objects\structures\door_assembly_types.dm" -#include "code\game\objects\structures\dresser.dm" -#include "code\game\objects\structures\electricchair.dm" -#include "code\game\objects\structures\extinguisher.dm" -#include "code\game\objects\structures\false_walls.dm" -#include "code\game\objects\structures\fence.dm" -#include "code\game\objects\structures\fireaxe.dm" -#include "code\game\objects\structures\fireplace.dm" -#include "code\game\objects\structures\flora.dm" -#include "code\game\objects\structures\fluff.dm" -#include "code\game\objects\structures\ghost_role_spawners.dm" -#include "code\game\objects\structures\girders.dm" -#include "code\game\objects\structures\grille.dm" -#include "code\game\objects\structures\guillotine.dm" -#include "code\game\objects\structures\guncase.dm" -#include "code\game\objects\structures\headpike.dm" -#include "code\game\objects\structures\hivebot.dm" -#include "code\game\objects\structures\holosign.dm" -#include "code\game\objects\structures\janicart.dm" -#include "code\game\objects\structures\kitchen_spike.dm" -#include "code\game\objects\structures\ladders.dm" -#include "code\game\objects\structures\lattice.dm" -#include "code\game\objects\structures\life_candle.dm" -#include "code\game\objects\structures\loom.dm" -#include "code\game\objects\structures\manned_turret.dm" -#include "code\game\objects\structures\medkit.dm" -#include "code\game\objects\structures\memorial.dm" -#include "code\game\objects\structures\mineral_doors.dm" -#include "code\game\objects\structures\mirror.dm" -#include "code\game\objects\structures\mop_bucket.dm" -#include "code\game\objects\structures\morgue.dm" -#include "code\game\objects\structures\musician.dm" -#include "code\game\objects\structures\noticeboard.dm" -#include "code\game\objects\structures\petrified_statue.dm" -#include "code\game\objects\structures\plasticflaps.dm" -#include "code\game\objects\structures\reflector.dm" -#include "code\game\objects\structures\safe.dm" -#include "code\game\objects\structures\showcase.dm" -#include "code\game\objects\structures\spawner.dm" -#include "code\game\objects\structures\spirit_board.dm" -#include "code\game\objects\structures\stairs.dm" -#include "code\game\objects\structures\statues.dm" -#include "code\game\objects\structures\table_frames.dm" -#include "code\game\objects\structures\tables_racks.dm" -#include "code\game\objects\structures\tank_dispenser.dm" -#include "code\game\objects\structures\target_stake.dm" -#include "code\game\objects\structures\traps.dm" -#include "code\game\objects\structures\trash_piles.dm" -#include "code\game\objects\structures\watercloset.dm" -#include "code\game\objects\structures\windoor_assembly.dm" -#include "code\game\objects\structures\window.dm" -#include "code\game\objects\structures\beds_chairs\alien_nest.dm" -#include "code\game\objects\structures\beds_chairs\bed.dm" -#include "code\game\objects\structures\beds_chairs\chair.dm" -#include "code\game\objects\structures\crates_lockers\closets.dm" -#include "code\game\objects\structures\crates_lockers\crates.dm" -#include "code\game\objects\structures\crates_lockers\closets\bodybag.dm" -#include "code\game\objects\structures\crates_lockers\closets\cardboardbox.dm" -#include "code\game\objects\structures\crates_lockers\closets\fitness.dm" -#include "code\game\objects\structures\crates_lockers\closets\gimmick.dm" -#include "code\game\objects\structures\crates_lockers\closets\job_closets.dm" -#include "code\game\objects\structures\crates_lockers\closets\l3closet.dm" -#include "code\game\objects\structures\crates_lockers\closets\syndicate.dm" -#include "code\game\objects\structures\crates_lockers\closets\utility_closets.dm" -#include "code\game\objects\structures\crates_lockers\closets\wardrobe.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\bar.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\cargo.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\engineering.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\freezer.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\hydroponics.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\medical.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\misc.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\personal.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\scientist.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\secure_closets.dm" -#include "code\game\objects\structures\crates_lockers\closets\secure\security.dm" -#include "code\game\objects\structures\crates_lockers\crates\bins.dm" -#include "code\game\objects\structures\crates_lockers\crates\critter.dm" -#include "code\game\objects\structures\crates_lockers\crates\large.dm" -#include "code\game\objects\structures\crates_lockers\crates\secure.dm" -#include "code\game\objects\structures\crates_lockers\crates\wooden.dm" -#include "code\game\objects\structures\lavaland\necropolis_tendril.dm" -#include "code\game\objects\structures\signs\_signs.dm" -#include "code\game\objects\structures\signs\signs_departments.dm" -#include "code\game\objects\structures\signs\signs_maps.dm" -#include "code\game\objects\structures\signs\signs_plaques.dm" -#include "code\game\objects\structures\signs\signs_warning.dm" -#include "code\game\objects\structures\transit_tubes\station.dm" -#include "code\game\objects\structures\transit_tubes\transit_tube.dm" -#include "code\game\objects\structures\transit_tubes\transit_tube_construction.dm" -#include "code\game\objects\structures\transit_tubes\transit_tube_pod.dm" -#include "code\game\turfs\baseturf_skipover.dm" -#include "code\game\turfs\change_turf.dm" -#include "code\game\turfs\closed.dm" -#include "code\game\turfs\open.dm" -#include "code\game\turfs\turf.dm" -#include "code\game\turfs\openspace\openspace.dm" -#include "code\game\turfs\simulated\chasm.dm" -#include "code\game\turfs\simulated\dirtystation.dm" -#include "code\game\turfs\simulated\floor.dm" -#include "code\game\turfs\simulated\lava.dm" -#include "code\game\turfs\simulated\minerals.dm" -#include "code\game\turfs\simulated\reebe_void.dm" -#include "code\game\turfs\simulated\river.dm" -#include "code\game\turfs\simulated\walls.dm" -#include "code\game\turfs\simulated\water.dm" -#include "code\game\turfs\simulated\floor\fancy_floor.dm" -#include "code\game\turfs\simulated\floor\light_floor.dm" -#include "code\game\turfs\simulated\floor\mineral_floor.dm" -#include "code\game\turfs\simulated\floor\misc_floor.dm" -#include "code\game\turfs\simulated\floor\plasteel_floor.dm" -#include "code\game\turfs\simulated\floor\plating.dm" -#include "code\game\turfs\simulated\floor\reinf_floor.dm" -#include "code\game\turfs\simulated\floor\plating\asteroid.dm" -#include "code\game\turfs\simulated\floor\plating\dirt.dm" -#include "code\game\turfs\simulated\floor\plating\misc_plating.dm" -#include "code\game\turfs\simulated\wall\mineral_walls.dm" -#include "code\game\turfs\simulated\wall\misc_walls.dm" -#include "code\game\turfs\simulated\wall\reinf_walls.dm" -#include "code\game\turfs\space\space.dm" -#include "code\game\turfs\space\transit.dm" -#include "code\modules\admin\admin.dm" -#include "code\modules\admin\admin_investigate.dm" -#include "code\modules\admin\admin_ranks.dm" -#include "code\modules\admin\admin_verbs.dm" -#include "code\modules\admin\adminmenu.dm" -#include "code\modules\admin\antag_panel.dm" -#include "code\modules\admin\banjob.dm" -#include "code\modules\admin\chat_commands.dm" -#include "code\modules\admin\check_antagonists.dm" -#include "code\modules\admin\create_mob.dm" -#include "code\modules\admin\create_object.dm" -#include "code\modules\admin\create_poll.dm" -#include "code\modules\admin\create_turf.dm" -#include "code\modules\admin\fun_balloon.dm" -#include "code\modules\admin\holder2.dm" -#include "code\modules\admin\ipintel.dm" -#include "code\modules\admin\IsBanned.dm" -#include "code\modules\admin\NewBan.dm" -#include "code\modules\admin\permissionedit.dm" -#include "code\modules\admin\player_panel.dm" -#include "code\modules\admin\secrets.dm" -#include "code\modules\admin\sound_emitter.dm" -#include "code\modules\admin\sql_message_system.dm" -#include "code\modules\admin\stickyban.dm" -#include "code\modules\admin\topic.dm" -#include "code\modules\admin\whitelist.dm" -#include "code\modules\admin\DB_ban\functions.dm" -#include "code\modules\admin\verbs\adminhelp.dm" -#include "code\modules\admin\verbs\adminjump.dm" -#include "code\modules\admin\verbs\adminpm.dm" -#include "code\modules\admin\verbs\adminsay.dm" -#include "code\modules\admin\verbs\ak47s.dm" -#include "code\modules\admin\verbs\atmosdebug.dm" -#include "code\modules\admin\verbs\bluespacearty.dm" -#include "code\modules\admin\verbs\borgpanel.dm" -#include "code\modules\admin\verbs\BrokenInhands.dm" -#include "code\modules\admin\verbs\cinematic.dm" -#include "code\modules\admin\verbs\deadsay.dm" -#include "code\modules\admin\verbs\debug.dm" -#include "code\modules\admin\verbs\diagnostics.dm" -#include "code\modules\admin\verbs\dice.dm" -#include "code\modules\admin\verbs\fps.dm" -#include "code\modules\admin\verbs\getlogs.dm" -#include "code\modules\admin\verbs\individual_logging.dm" -#include "code\modules\admin\verbs\machine_upgrade.dm" -#include "code\modules\admin\verbs\manipulate_organs.dm" -#include "code\modules\admin\verbs\map_template_loadverb.dm" -#include "code\modules\admin\verbs\mapping.dm" -#include "code\modules\admin\verbs\maprotation.dm" -#include "code\modules\admin\verbs\massmodvar.dm" -#include "code\modules\admin\verbs\modifyvariables.dm" -#include "code\modules\admin\verbs\one_click_antag.dm" -#include "code\modules\admin\verbs\onlyone.dm" -#include "code\modules\admin\verbs\panicbunker.dm" -#include "code\modules\admin\verbs\playsound.dm" -#include "code\modules\admin\verbs\possess.dm" -#include "code\modules\admin\verbs\pray.dm" -#include "code\modules\admin\verbs\randomverbs.dm" -#include "code\modules\admin\verbs\reestablish_db_connection.dm" -#include "code\modules\admin\verbs\spawnfloorcluwne.dm" -#include "code\modules\admin\verbs\spawnobjasmob.dm" -#include "code\modules\admin\verbs\tripAI.dm" -#include "code\modules\admin\verbs\SDQL2\SDQL_2.dm" -#include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm" -#include "code\modules\admin\verbs\SDQL2\SDQL_2_wrappers.dm" -#include "code\modules\antagonists\_common\antag_datum.dm" -#include "code\modules\antagonists\_common\antag_helpers.dm" -#include "code\modules\antagonists\_common\antag_hud.dm" -#include "code\modules\antagonists\_common\antag_spawner.dm" -#include "code\modules\antagonists\_common\antag_team.dm" -#include "code\modules\antagonists\abductor\abductor.dm" -#include "code\modules\antagonists\abductor\abductee\abductee_objectives.dm" -#include "code\modules\antagonists\abductor\equipment\abduction_gear.dm" -#include "code\modules\antagonists\abductor\equipment\abduction_outfits.dm" -#include "code\modules\antagonists\abductor\equipment\abduction_surgery.dm" -#include "code\modules\antagonists\abductor\equipment\gland.dm" -#include "code\modules\antagonists\abductor\machinery\camera.dm" -#include "code\modules\antagonists\abductor\machinery\console.dm" -#include "code\modules\antagonists\abductor\machinery\dispenser.dm" -#include "code\modules\antagonists\abductor\machinery\experiment.dm" -#include "code\modules\antagonists\abductor\machinery\pad.dm" -#include "code\modules\antagonists\blob\blob.dm" -#include "code\modules\antagonists\blob\blob\blob_report.dm" -#include "code\modules\antagonists\blob\blob\overmind.dm" -#include "code\modules\antagonists\blob\blob\powers.dm" -#include "code\modules\antagonists\blob\blob\theblob.dm" -#include "code\modules\antagonists\blob\blob\blobs\blob_mobs.dm" -#include "code\modules\antagonists\blob\blob\blobs\core.dm" -#include "code\modules\antagonists\blob\blob\blobs\factory.dm" -#include "code\modules\antagonists\blob\blob\blobs\node.dm" -#include "code\modules\antagonists\blob\blob\blobs\resource.dm" -#include "code\modules\antagonists\blob\blob\blobs\shield.dm" -#include "code\modules\antagonists\bloodsucker\bloodsucker_flaws.dm" -#include "code\modules\antagonists\bloodsucker\bloodsucker_integration.dm" -#include "code\modules\antagonists\bloodsucker\bloodsucker_life.dm" -#include "code\modules\antagonists\bloodsucker\bloodsucker_objectives.dm" -#include "code\modules\antagonists\bloodsucker\bloodsucker_powers.dm" -#include "code\modules\antagonists\bloodsucker\bloodsucker_sunlight.dm" -#include "code\modules\antagonists\bloodsucker\bloodsucker_ui.dm" -#include "code\modules\antagonists\bloodsucker\datum_bloodsucker.dm" -#include "code\modules\antagonists\bloodsucker\datum_hunter.dm" -#include "code\modules\antagonists\bloodsucker\datum_vassal.dm" -#include "code\modules\antagonists\bloodsucker\items\bloodsucker_organs.dm" -#include "code\modules\antagonists\bloodsucker\items\bloodsucker_stake.dm" -#include "code\modules\antagonists\bloodsucker\objects\bloodsucker_coffin.dm" -#include "code\modules\antagonists\bloodsucker\objects\bloodsucker_crypt.dm" -#include "code\modules\antagonists\bloodsucker\objects\bloodsucker_lair.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_brawn.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_cloak.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_feed.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_fortitude.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_gohome.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_haste.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_lunge.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_masquerade.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_mesmerize.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_trespass.dm" -#include "code\modules\antagonists\bloodsucker\powers\bs_veil.dm" -#include "code\modules\antagonists\bloodsucker\powers\v_recuperate.dm" -#include "code\modules\antagonists\brainwashing\brainwashing.dm" -#include "code\modules\antagonists\brother\brother.dm" -#include "code\modules\antagonists\changeling\cellular_emporium.dm" -#include "code\modules\antagonists\changeling\changeling.dm" -#include "code\modules\antagonists\changeling\changeling_power.dm" -#include "code\modules\antagonists\changeling\powers\absorb.dm" -#include "code\modules\antagonists\changeling\powers\adrenaline.dm" -#include "code\modules\antagonists\changeling\powers\augmented_eyesight.dm" -#include "code\modules\antagonists\changeling\powers\biodegrade.dm" -#include "code\modules\antagonists\changeling\powers\chameleon_skin.dm" -#include "code\modules\antagonists\changeling\powers\digitalcamo.dm" -#include "code\modules\antagonists\changeling\powers\fakedeath.dm" -#include "code\modules\antagonists\changeling\powers\fleshmend.dm" -#include "code\modules\antagonists\changeling\powers\headcrab.dm" -#include "code\modules\antagonists\changeling\powers\hivemind.dm" -#include "code\modules\antagonists\changeling\powers\humanform.dm" -#include "code\modules\antagonists\changeling\powers\lesserform.dm" -#include "code\modules\antagonists\changeling\powers\linglink.dm" -#include "code\modules\antagonists\changeling\powers\mimic_voice.dm" -#include "code\modules\antagonists\changeling\powers\mutations.dm" -#include "code\modules\antagonists\changeling\powers\panacea.dm" -#include "code\modules\antagonists\changeling\powers\pheromone_receptors.dm" -#include "code\modules\antagonists\changeling\powers\regenerate.dm" -#include "code\modules\antagonists\changeling\powers\revive.dm" -#include "code\modules\antagonists\changeling\powers\shriek.dm" -#include "code\modules\antagonists\changeling\powers\spiders.dm" -#include "code\modules\antagonists\changeling\powers\strained_muscles.dm" -#include "code\modules\antagonists\changeling\powers\tiny_prick.dm" -#include "code\modules\antagonists\changeling\powers\transform.dm" -#include "code\modules\antagonists\clockcult\clock_effect.dm" -#include "code\modules\antagonists\clockcult\clock_item.dm" -#include "code\modules\antagonists\clockcult\clock_mobs.dm" -#include "code\modules\antagonists\clockcult\clock_scripture.dm" -#include "code\modules\antagonists\clockcult\clock_structure.dm" -#include "code\modules\antagonists\clockcult\clockcult.dm" -#include "code\modules\antagonists\clockcult\clock_effects\city_of_cogs_rift.dm" -#include "code\modules\antagonists\clockcult\clock_effects\clock_overlay.dm" -#include "code\modules\antagonists\clockcult\clock_effects\clock_sigils.dm" -#include "code\modules\antagonists\clockcult\clock_effects\general_markers.dm" -#include "code\modules\antagonists\clockcult\clock_effects\servant_blocker.dm" -#include "code\modules\antagonists\clockcult\clock_effects\spatial_gateway.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\clock_powerdrain.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\component_helpers.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\fabrication_helpers.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\hierophant_network.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\power_helpers.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\ratvarian_language.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\scripture_checks.dm" -#include "code\modules\antagonists\clockcult\clock_helpers\slab_abilities.dm" -#include "code\modules\antagonists\clockcult\clock_items\clock_components.dm" -#include "code\modules\antagonists\clockcult\clock_items\clockwork_armor.dm" -#include "code\modules\antagonists\clockcult\clock_items\clockwork_slab.dm" -#include "code\modules\antagonists\clockcult\clock_items\clockwork_weaponry.dm" -#include "code\modules\antagonists\clockcult\clock_items\construct_chassis.dm" -#include "code\modules\antagonists\clockcult\clock_items\integration_cog.dm" -#include "code\modules\antagonists\clockcult\clock_items\judicial_visor.dm" -#include "code\modules\antagonists\clockcult\clock_items\replica_fabricator.dm" -#include "code\modules\antagonists\clockcult\clock_items\soul_vessel.dm" -#include "code\modules\antagonists\clockcult\clock_items\wraith_spectacles.dm" -#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\_call_weapon.dm" -#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\ratvarian_spear.dm" -#include "code\modules\antagonists\clockcult\clock_mobs\_eminence.dm" -#include "code\modules\antagonists\clockcult\clock_mobs\clockwork_marauder.dm" -#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_applications.dm" -#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_cyborg.dm" -#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_drivers.dm" -#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_scripts.dm" -#include "code\modules\antagonists\clockcult\clock_structures\_trap_object.dm" -#include "code\modules\antagonists\clockcult\clock_structures\ark_of_the_clockwork_justicar.dm" -#include "code\modules\antagonists\clockcult\clock_structures\clockwork_obelisk.dm" -#include "code\modules\antagonists\clockcult\clock_structures\eminence_spire.dm" -#include "code\modules\antagonists\clockcult\clock_structures\heralds_beacon.dm" -#include "code\modules\antagonists\clockcult\clock_structures\mania_motor.dm" -#include "code\modules\antagonists\clockcult\clock_structures\ocular_warden.dm" -#include "code\modules\antagonists\clockcult\clock_structures\ratvar_the_clockwork_justicar.dm" -#include "code\modules\antagonists\clockcult\clock_structures\reflector.dm" -#include "code\modules\antagonists\clockcult\clock_structures\stargazer.dm" -#include "code\modules\antagonists\clockcult\clock_structures\taunting_trail.dm" -#include "code\modules\antagonists\clockcult\clock_structures\wall_gear.dm" -#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\lever.dm" -#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\pressure_sensor.dm" -#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\pressure_sensor_mech.dm" -#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\repeater.dm" -#include "code\modules\antagonists\clockcult\clock_structures\traps\brass_skewer.dm" -#include "code\modules\antagonists\clockcult\clock_structures\traps\power_null.dm" -#include "code\modules\antagonists\clockcult\clock_structures\traps\steam_vent.dm" -#include "code\modules\antagonists\cult\blood_magic.dm" -#include "code\modules\antagonists\cult\cult.dm" -#include "code\modules\antagonists\cult\cult_comms.dm" -#include "code\modules\antagonists\cult\cult_items.dm" -#include "code\modules\antagonists\cult\cult_structures.dm" -#include "code\modules\antagonists\cult\ritual.dm" -#include "code\modules\antagonists\cult\rune_spawn_action.dm" -#include "code\modules\antagonists\cult\runes.dm" -#include "code\modules\antagonists\devil\devil.dm" -#include "code\modules\antagonists\devil\devil_helpers.dm" -#include "code\modules\antagonists\devil\imp\imp.dm" -#include "code\modules\antagonists\devil\sintouched\objectives.dm" -#include "code\modules\antagonists\devil\sintouched\sintouched.dm" -#include "code\modules\antagonists\devil\true_devil\_true_devil.dm" -#include "code\modules\antagonists\devil\true_devil\inventory.dm" -#include "code\modules\antagonists\disease\disease_abilities.dm" -#include "code\modules\antagonists\disease\disease_datum.dm" -#include "code\modules\antagonists\disease\disease_disease.dm" -#include "code\modules\antagonists\disease\disease_event.dm" -#include "code\modules\antagonists\disease\disease_mob.dm" -#include "code\modules\antagonists\ert\ert.dm" -#include "code\modules\antagonists\greentext\greentext.dm" -#include "code\modules\antagonists\greybois\greybois.dm" -#include "code\modules\antagonists\highlander\highlander.dm" -#include "code\modules\antagonists\monkey\monkey.dm" -#include "code\modules\antagonists\morph\morph.dm" -#include "code\modules\antagonists\morph\morph_antag.dm" -#include "code\modules\antagonists\nightmare\nightmare.dm" -#include "code\modules\antagonists\ninja\ninja.dm" -#include "code\modules\antagonists\nukeop\clownop.dm" -#include "code\modules\antagonists\nukeop\nukeop.dm" -#include "code\modules\antagonists\nukeop\equipment\nuclear_challenge.dm" -#include "code\modules\antagonists\nukeop\equipment\nuclearbomb.dm" -#include "code\modules\antagonists\nukeop\equipment\pinpointer.dm" -#include "code\modules\antagonists\official\official.dm" -#include "code\modules\antagonists\overthrow\overthrow.dm" -#include "code\modules\antagonists\overthrow\overthrow_converter.dm" -#include "code\modules\antagonists\overthrow\overthrow_team.dm" -#include "code\modules\antagonists\pirate\pirate.dm" -#include "code\modules\antagonists\revenant\revenant.dm" -#include "code\modules\antagonists\revenant\revenant_abilities.dm" -#include "code\modules\antagonists\revenant\revenant_antag.dm" -#include "code\modules\antagonists\revenant\revenant_blight.dm" -#include "code\modules\antagonists\revenant\revenant_spawn_event.dm" -#include "code\modules\antagonists\revolution\revolution.dm" -#include "code\modules\antagonists\separatist\separatist.dm" -#include "code\modules\antagonists\slaughter\slaughter.dm" -#include "code\modules\antagonists\slaughter\slaughter_antag.dm" -#include "code\modules\antagonists\slaughter\slaughterevent.dm" -#include "code\modules\antagonists\survivalist\survivalist.dm" -#include "code\modules\antagonists\swarmer\swarmer.dm" -#include "code\modules\antagonists\swarmer\swarmer_event.dm" -#include "code\modules\antagonists\traitor\datum_traitor.dm" -#include "code\modules\antagonists\traitor\equipment\Malf_Modules.dm" -#include "code\modules\antagonists\traitor\IAA\internal_affairs.dm" -#include "code\modules\antagonists\valentines\heartbreaker.dm" -#include "code\modules\antagonists\valentines\valentine.dm" -#include "code\modules\antagonists\wishgranter\wishgranter.dm" -#include "code\modules\antagonists\wizard\wizard.dm" -#include "code\modules\antagonists\wizard\equipment\artefact.dm" -#include "code\modules\antagonists\wizard\equipment\soulstone.dm" -#include "code\modules\antagonists\wizard\equipment\spellbook.dm" -#include "code\modules\antagonists\xeno\xeno.dm" -#include "code\modules\assembly\assembly.dm" -#include "code\modules\assembly\bomb.dm" -#include "code\modules\assembly\doorcontrol.dm" -#include "code\modules\assembly\flash.dm" -#include "code\modules\assembly\health.dm" -#include "code\modules\assembly\helpers.dm" -#include "code\modules\assembly\holder.dm" -#include "code\modules\assembly\igniter.dm" -#include "code\modules\assembly\infrared.dm" -#include "code\modules\assembly\mousetrap.dm" -#include "code\modules\assembly\proximity.dm" -#include "code\modules\assembly\shock_kit.dm" -#include "code\modules\assembly\signaler.dm" -#include "code\modules\assembly\timer.dm" -#include "code\modules\assembly\voice.dm" -#include "code\modules\atmospherics\multiz.dm" -#include "code\modules\atmospherics\environmental\LINDA_fire.dm" -#include "code\modules\atmospherics\environmental\LINDA_system.dm" -#include "code\modules\atmospherics\environmental\LINDA_turf_tile.dm" -#include "code\modules\atmospherics\gasmixtures\gas_mixture.dm" -#include "code\modules\atmospherics\gasmixtures\gas_types.dm" -#include "code\modules\atmospherics\gasmixtures\immutable_mixtures.dm" -#include "code\modules\atmospherics\gasmixtures\reactions.dm" -#include "code\modules\atmospherics\machinery\airalarm.dm" -#include "code\modules\atmospherics\machinery\atmosmachinery.dm" -#include "code\modules\atmospherics\machinery\datum_pipeline.dm" -#include "code\modules\atmospherics\machinery\components\components_base.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\binary_devices.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\circulator.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\dp_vent_pump.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\passive_gate.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\pump.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\relief_valve.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\valve.dm" -#include "code\modules\atmospherics\machinery\components\binary_devices\volume_pump.dm" -#include "code\modules\atmospherics\machinery\components\trinary_devices\filter.dm" -#include "code\modules\atmospherics\machinery\components\trinary_devices\mixer.dm" -#include "code\modules\atmospherics\machinery\components\trinary_devices\trinary_devices.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\cryo.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\heat_exchanger.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\outlet_injector.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\passive_vent.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\portables_connector.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\relief_valve.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\tank.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\thermomachine.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\unary_devices.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\vent_pump.dm" -#include "code\modules\atmospherics\machinery\components\unary_devices\vent_scrubber.dm" -#include "code\modules\atmospherics\machinery\other\meter.dm" -#include "code\modules\atmospherics\machinery\other\miner.dm" -#include "code\modules\atmospherics\machinery\pipes\layermanifold.dm" -#include "code\modules\atmospherics\machinery\pipes\manifold.dm" -#include "code\modules\atmospherics\machinery\pipes\manifold4w.dm" -#include "code\modules\atmospherics\machinery\pipes\mapping.dm" -#include "code\modules\atmospherics\machinery\pipes\pipes.dm" -#include "code\modules\atmospherics\machinery\pipes\simple.dm" -#include "code\modules\atmospherics\machinery\pipes\heat_exchange\he_pipes.dm" -#include "code\modules\atmospherics\machinery\pipes\heat_exchange\junction.dm" -#include "code\modules\atmospherics\machinery\pipes\heat_exchange\manifold.dm" -#include "code\modules\atmospherics\machinery\pipes\heat_exchange\manifold4w.dm" -#include "code\modules\atmospherics\machinery\pipes\heat_exchange\simple.dm" -#include "code\modules\atmospherics\machinery\portable\canister.dm" -#include "code\modules\atmospherics\machinery\portable\portable_atmospherics.dm" -#include "code\modules\atmospherics\machinery\portable\pump.dm" -#include "code\modules\atmospherics\machinery\portable\scrubber.dm" -#include "code\modules\awaymissions\away_props.dm" -#include "code\modules\awaymissions\bluespaceartillery.dm" -#include "code\modules\awaymissions\capture_the_flag.dm" -#include "code\modules\awaymissions\corpse.dm" -#include "code\modules\awaymissions\exile.dm" -#include "code\modules\awaymissions\gateway.dm" -#include "code\modules\awaymissions\pamphlet.dm" -#include "code\modules\awaymissions\signpost.dm" -#include "code\modules\awaymissions\super_secret_room.dm" -#include "code\modules\awaymissions\zlevel.dm" -#include "code\modules\awaymissions\mission_code\Academy.dm" -#include "code\modules\awaymissions\mission_code\Cabin.dm" -#include "code\modules\awaymissions\mission_code\caves.dm" -#include "code\modules\awaymissions\mission_code\centcomAway.dm" -#include "code\modules\awaymissions\mission_code\challenge.dm" -#include "code\modules\awaymissions\mission_code\moonoutpost19.dm" -#include "code\modules\awaymissions\mission_code\murderdome.dm" -#include "code\modules\awaymissions\mission_code\research.dm" -#include "code\modules\awaymissions\mission_code\snowdin.dm" -#include "code\modules\awaymissions\mission_code\spacebattle.dm" -#include "code\modules\awaymissions\mission_code\stationCollision.dm" -#include "code\modules\awaymissions\mission_code\undergroundoutpost45.dm" -#include "code\modules\awaymissions\mission_code\wildwest.dm" -#include "code\modules\bsql\includes.dm" -#include "code\modules\buildmode\bm_mode.dm" -#include "code\modules\buildmode\buildmode.dm" -#include "code\modules\buildmode\buttons.dm" -#include "code\modules\buildmode\effects\line.dm" -#include "code\modules\buildmode\submodes\advanced.dm" -#include "code\modules\buildmode\submodes\area_edit.dm" -#include "code\modules\buildmode\submodes\basic.dm" -#include "code\modules\buildmode\submodes\boom.dm" -#include "code\modules\buildmode\submodes\copy.dm" -#include "code\modules\buildmode\submodes\fill.dm" -#include "code\modules\buildmode\submodes\mapgen.dm" -#include "code\modules\buildmode\submodes\throwing.dm" -#include "code\modules\buildmode\submodes\variable_edit.dm" -#include "code\modules\cargo\bounty.dm" -#include "code\modules\cargo\bounty_console.dm" -#include "code\modules\cargo\centcom_podlauncher.dm" -#include "code\modules\cargo\console.dm" -#include "code\modules\cargo\export_scanner.dm" -#include "code\modules\cargo\exports.dm" -#include "code\modules\cargo\expressconsole.dm" -#include "code\modules\cargo\gondolapod.dm" -#include "code\modules\cargo\order.dm" -#include "code\modules\cargo\packs.dm" -#include "code\modules\cargo\supplypod.dm" -#include "code\modules\cargo\supplypod_beacon.dm" -#include "code\modules\cargo\bounties\assistant.dm" -#include "code\modules\cargo\bounties\botany.dm" -#include "code\modules\cargo\bounties\chef.dm" -#include "code\modules\cargo\bounties\engineering.dm" -#include "code\modules\cargo\bounties\item.dm" -#include "code\modules\cargo\bounties\mech.dm" -#include "code\modules\cargo\bounties\medical.dm" -#include "code\modules\cargo\bounties\mining.dm" -#include "code\modules\cargo\bounties\reagent.dm" -#include "code\modules\cargo\bounties\science.dm" -#include "code\modules\cargo\bounties\security.dm" -#include "code\modules\cargo\bounties\slime.dm" -#include "code\modules\cargo\bounties\special.dm" -#include "code\modules\cargo\bounties\virus.dm" -#include "code\modules\cargo\exports\food_wine.dm" -#include "code\modules\cargo\exports\gear.dm" -#include "code\modules\cargo\exports\large_objects.dm" -#include "code\modules\cargo\exports\manifest.dm" -#include "code\modules\cargo\exports\materials.dm" -#include "code\modules\cargo\exports\organs_robotics.dm" -#include "code\modules\cargo\exports\parts.dm" -#include "code\modules\cargo\exports\seeds.dm" -#include "code\modules\cargo\exports\sheets.dm" -#include "code\modules\cargo\exports\tools.dm" -#include "code\modules\cargo\exports\weapons.dm" -#include "code\modules\cargo\packs\armory.dm" -#include "code\modules\cargo\packs\costumes_toys.dm" -#include "code\modules\cargo\packs\emergency.dm" -#include "code\modules\cargo\packs\engine.dm" -#include "code\modules\cargo\packs\engineering.dm" -#include "code\modules\cargo\packs\livestock.dm" -#include "code\modules\cargo\packs\materials.dm" -#include "code\modules\cargo\packs\medical.dm" -#include "code\modules\cargo\packs\misc.dm" -#include "code\modules\cargo\packs\organic.dm" -#include "code\modules\cargo\packs\science.dm" -#include "code\modules\cargo\packs\security.dm" -#include "code\modules\cargo\packs\service.dm" -#include "code\modules\chatter\chatter.dm" -#include "code\modules\client\asset_cache.dm" -#include "code\modules\client\client_colour.dm" -#include "code\modules\client\client_defines.dm" -#include "code\modules\client\client_procs.dm" -#include "code\modules\client\darkmode.dm" -#include "code\modules\client\message.dm" -#include "code\modules\client\player_details.dm" -#include "code\modules\client\preferences.dm" -#include "code\modules\client\preferences_savefile.dm" -#include "code\modules\client\preferences_toggles.dm" -#include "code\modules\client\preferences_vr.dm" -#include "code\modules\client\verbs\aooc.dm" -#include "code\modules\client\verbs\etips.dm" -#include "code\modules\client\verbs\looc.dm" -#include "code\modules\client\verbs\ooc.dm" -#include "code\modules\client\verbs\ping.dm" -#include "code\modules\client\verbs\suicide.dm" -#include "code\modules\client\verbs\who.dm" -#include "code\modules\clothing\chameleon.dm" -#include "code\modules\clothing\clothing.dm" -#include "code\modules\clothing\ears\_ears.dm" -#include "code\modules\clothing\glasses\_glasses.dm" -#include "code\modules\clothing\glasses\engine_goggles.dm" -#include "code\modules\clothing\glasses\hud.dm" -#include "code\modules\clothing\glasses\vg_glasses.dm" -#include "code\modules\clothing\gloves\_gloves.dm" -#include "code\modules\clothing\gloves\boxing.dm" -#include "code\modules\clothing\gloves\color.dm" -#include "code\modules\clothing\gloves\miscellaneous.dm" -#include "code\modules\clothing\gloves\ring.dm" -#include "code\modules\clothing\gloves\vg_gloves.dm" -#include "code\modules\clothing\head\_head.dm" -#include "code\modules\clothing\head\beanie.dm" -#include "code\modules\clothing\head\cit_hats.dm" -#include "code\modules\clothing\head\collectable.dm" -#include "code\modules\clothing\head\hardhat.dm" -#include "code\modules\clothing\head\helmet.dm" -#include "code\modules\clothing\head\jobs.dm" -#include "code\modules\clothing\head\misc.dm" -#include "code\modules\clothing\head\misc_special.dm" -#include "code\modules\clothing\head\soft_caps.dm" -#include "code\modules\clothing\head\vg_hats.dm" -#include "code\modules\clothing\masks\_masks.dm" -#include "code\modules\clothing\masks\boxing.dm" -#include "code\modules\clothing\masks\breath.dm" -#include "code\modules\clothing\masks\gasmask.dm" -#include "code\modules\clothing\masks\hailer.dm" -#include "code\modules\clothing\masks\miscellaneous.dm" -#include "code\modules\clothing\masks\vg_masks.dm" -#include "code\modules\clothing\neck\_neck.dm" -#include "code\modules\clothing\outfits\ert.dm" -#include "code\modules\clothing\outfits\event.dm" -#include "code\modules\clothing\outfits\plasmaman.dm" -#include "code\modules\clothing\outfits\standard.dm" -#include "code\modules\clothing\outfits\vr.dm" -#include "code\modules\clothing\outfits\vv_outfit.dm" -#include "code\modules\clothing\shoes\_shoes.dm" -#include "code\modules\clothing\shoes\bananashoes.dm" -#include "code\modules\clothing\shoes\colour.dm" -#include "code\modules\clothing\shoes\magboots.dm" -#include "code\modules\clothing\shoes\miscellaneous.dm" -#include "code\modules\clothing\shoes\taeclowndo.dm" -#include "code\modules\clothing\shoes\vg_shoes.dm" -#include "code\modules\clothing\spacesuits\_spacesuits.dm" -#include "code\modules\clothing\spacesuits\chronosuit.dm" -#include "code\modules\clothing\spacesuits\hardsuit.dm" -#include "code\modules\clothing\spacesuits\miscellaneous.dm" -#include "code\modules\clothing\spacesuits\plasmamen.dm" -#include "code\modules\clothing\spacesuits\syndi.dm" -#include "code\modules\clothing\spacesuits\vg_spess.dm" -#include "code\modules\clothing\suits\_suits.dm" -#include "code\modules\clothing\suits\armor.dm" -#include "code\modules\clothing\suits\bio.dm" -#include "code\modules\clothing\suits\cloaks.dm" -#include "code\modules\clothing\suits\jobs.dm" -#include "code\modules\clothing\suits\labcoat.dm" -#include "code\modules\clothing\suits\miscellaneous.dm" -#include "code\modules\clothing\suits\reactive_armour.dm" -#include "code\modules\clothing\suits\toggles.dm" -#include "code\modules\clothing\suits\utility.dm" -#include "code\modules\clothing\suits\vg_suits.dm" -#include "code\modules\clothing\suits\wiz_robe.dm" -#include "code\modules\clothing\under\_under.dm" -#include "code\modules\clothing\under\accessories.dm" -#include "code\modules\clothing\under\color.dm" -#include "code\modules\clothing\under\miscellaneous.dm" -#include "code\modules\clothing\under\pants.dm" -#include "code\modules\clothing\under\shorts.dm" -#include "code\modules\clothing\under\syndicate.dm" -#include "code\modules\clothing\under\trek.dm" -#include "code\modules\clothing\under\vg_under.dm" -#include "code\modules\clothing\under\jobs\civilian.dm" -#include "code\modules\clothing\under\jobs\engineering.dm" -#include "code\modules\clothing\under\jobs\medsci.dm" -#include "code\modules\clothing\under\jobs\security.dm" -#include "code\modules\clothing\under\jobs\Plasmaman\civilian_service.dm" -#include "code\modules\clothing\under\jobs\Plasmaman\engineering.dm" -#include "code\modules\clothing\under\jobs\Plasmaman\medsci.dm" -#include "code\modules\clothing\under\jobs\Plasmaman\security.dm" -#include "code\modules\crafting\craft.dm" -#include "code\modules\crafting\guncrafting.dm" -#include "code\modules\crafting\recipes.dm" -#include "code\modules\crafting\recipes\recipes_clothing.dm" -#include "code\modules\crafting\recipes\recipes_misc.dm" -#include "code\modules\crafting\recipes\recipes_primal.dm" -#include "code\modules\crafting\recipes\recipes_robot.dm" -#include "code\modules\crafting\recipes\recipes_weapon_and_ammo.dm" -#include "code\modules\detectivework\detective_work.dm" -#include "code\modules\detectivework\evidence.dm" -#include "code\modules\detectivework\scanner.dm" -#include "code\modules\emoji\emoji_parse.dm" -#include "code\modules\error_handler\error_handler.dm" -#include "code\modules\error_handler\error_viewer.dm" -#include "code\modules\events\_event.dm" -#include "code\modules\events\abductor.dm" -#include "code\modules\events\alien_infestation.dm" -#include "code\modules\events\anomaly.dm" -#include "code\modules\events\anomaly_bluespace.dm" -#include "code\modules\events\anomaly_flux.dm" -#include "code\modules\events\anomaly_grav.dm" -#include "code\modules\events\anomaly_pyro.dm" -#include "code\modules\events\anomaly_vortex.dm" -#include "code\modules\events\aurora_aquilae.dm" -#include "code\modules\events\aurora_caelus.dm" -#include "code\modules\events\blob.dm" -#include "code\modules\events\brand_intelligence.dm" -#include "code\modules\events\bureaucratic_error.dm" -#include "code\modules\events\camerafailure.dm" -#include "code\modules\events\carp_migration.dm" -#include "code\modules\events\carpteam.dm" -#include "code\modules\events\communications_blackout.dm" -#include "code\modules\events\devil.dm" -#include "code\modules\events\disease_outbreak.dm" -#include "code\modules\events\dust.dm" -#include "code\modules\events\electrical_storm.dm" -#include "code\modules\events\false_alarm.dm" -#include "code\modules\events\floorcluwne.dm" -#include "code\modules\events\ghost_role.dm" -#include "code\modules\events\grid_check.dm" -#include "code\modules\events\heart_attack.dm" -#include "code\modules\events\high_priority_bounty.dm" -#include "code\modules\events\immovable_rod.dm" -#include "code\modules\events\ion_storm.dm" -#include "code\modules\events\major_dust.dm" -#include "code\modules\events\mass_hallucination.dm" -#include "code\modules\events\meateor_wave.dm" -#include "code\modules\events\meteor_wave.dm" -#include "code\modules\events\mice_migration.dm" -#include "code\modules\events\nightmare.dm" -#include "code\modules\events\operative.dm" -#include "code\modules\events\pirates.dm" -#include "code\modules\events\portal_storm.dm" -#include "code\modules\events\prison_break.dm" -#include "code\modules\events\processor_overload.dm" -#include "code\modules\events\radiation_storm.dm" -#include "code\modules\events\sentience.dm" -#include "code\modules\events\shuttle_loan.dm" -#include "code\modules\events\spacevine.dm" -#include "code\modules\events\spider_infestation.dm" -#include "code\modules\events\spontaneous_appendicitis.dm" -#include "code\modules\events\vent_clog.dm" -#include "code\modules\events\wormholes.dm" -#include "code\modules\events\holiday\halloween.dm" -#include "code\modules\events\holiday\vday.dm" -#include "code\modules\events\holiday\xmas.dm" -#include "code\modules\events\wizard\aid.dm" -#include "code\modules\events\wizard\blobies.dm" -#include "code\modules\events\wizard\curseditems.dm" -#include "code\modules\events\wizard\departmentrevolt.dm" -#include "code\modules\events\wizard\fakeexplosion.dm" -#include "code\modules\events\wizard\ghost.dm" -#include "code\modules\events\wizard\greentext.dm" -#include "code\modules\events\wizard\imposter.dm" -#include "code\modules\events\wizard\invincible.dm" -#include "code\modules\events\wizard\lava.dm" -#include "code\modules\events\wizard\magicarp.dm" -#include "code\modules\events\wizard\petsplosion.dm" -#include "code\modules\events\wizard\race.dm" -#include "code\modules\events\wizard\rpgloot.dm" -#include "code\modules\events\wizard\shuffle.dm" -#include "code\modules\events\wizard\summons.dm" -#include "code\modules\fields\fields.dm" -#include "code\modules\fields\gravity.dm" -#include "code\modules\fields\peaceborg_dampener.dm" -#include "code\modules\fields\timestop.dm" -#include "code\modules\fields\turf_objects.dm" -#include "code\modules\flufftext\Dreaming.dm" -#include "code\modules\flufftext\Hallucination.dm" -#include "code\modules\food_and_drinks\autobottler.dm" -#include "code\modules\food_and_drinks\food.dm" -#include "code\modules\food_and_drinks\pizzabox.dm" -#include "code\modules\food_and_drinks\drinks\drinks.dm" -#include "code\modules\food_and_drinks\drinks\drinks\bottle.dm" -#include "code\modules\food_and_drinks\drinks\drinks\drinkingglass.dm" -#include "code\modules\food_and_drinks\food\condiment.dm" -#include "code\modules\food_and_drinks\food\customizables.dm" -#include "code\modules\food_and_drinks\food\snacks.dm" -#include "code\modules\food_and_drinks\food\snacks_bread.dm" -#include "code\modules\food_and_drinks\food\snacks_burgers.dm" -#include "code\modules\food_and_drinks\food\snacks_cake.dm" -#include "code\modules\food_and_drinks\food\snacks_egg.dm" -#include "code\modules\food_and_drinks\food\snacks_frozen.dm" -#include "code\modules\food_and_drinks\food\snacks_meat.dm" -#include "code\modules\food_and_drinks\food\snacks_other.dm" -#include "code\modules\food_and_drinks\food\snacks_pastry.dm" -#include "code\modules\food_and_drinks\food\snacks_pie.dm" -#include "code\modules\food_and_drinks\food\snacks_pizza.dm" -#include "code\modules\food_and_drinks\food\snacks_salad.dm" -#include "code\modules\food_and_drinks\food\snacks_sandwichtoast.dm" -#include "code\modules\food_and_drinks\food\snacks_soup.dm" -#include "code\modules\food_and_drinks\food\snacks_spaghetti.dm" -#include "code\modules\food_and_drinks\food\snacks_sushi.dm" -#include "code\modules\food_and_drinks\food\snacks_vend.dm" -#include "code\modules\food_and_drinks\food\snacks\dough.dm" -#include "code\modules\food_and_drinks\food\snacks\meat.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\deep_fryer.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\food_cart.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\gibber.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\grill.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\icecream_vat.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\microwave.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\monkeyrecycler.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\processor.dm" -#include "code\modules\food_and_drinks\kitchen_machinery\smartfridge.dm" -#include "code\modules\food_and_drinks\recipes\drinks_recipes.dm" -#include "code\modules\food_and_drinks\recipes\food_mixtures.dm" -#include "code\modules\food_and_drinks\recipes\processor_recipes.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_bread.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_burger.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_cake.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_egg.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_frozen.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_meat.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_misc.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_pastry.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_pie.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_pizza.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_salad.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_sandwich.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_soup.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_spaghetti.dm" -#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_sushi.dm" -#include "code\modules\games\cas.dm" -#include "code\modules\goonchat\browserOutput.dm" -#include "code\modules\goonchat\jsErrorHandler.dm" -#include "code\modules\holiday\easter.dm" -#include "code\modules\holiday\holidays.dm" -#include "code\modules\holiday\halloween\bartholomew.dm" -#include "code\modules\holiday\halloween\jacqueen.dm" -#include "code\modules\holodeck\area_copy.dm" -#include "code\modules\holodeck\computer.dm" -#include "code\modules\holodeck\holo_effect.dm" -#include "code\modules\holodeck\items.dm" -#include "code\modules\holodeck\mobs.dm" -#include "code\modules\holodeck\turfs.dm" -#include "code\modules\hydroponics\biogenerator.dm" -#include "code\modules\hydroponics\fermenting_barrel.dm" -#include "code\modules\hydroponics\gene_modder.dm" -#include "code\modules\hydroponics\grown.dm" -#include "code\modules\hydroponics\growninedible.dm" -#include "code\modules\hydroponics\hydroitemdefines.dm" -#include "code\modules\hydroponics\hydroponics.dm" -#include "code\modules\hydroponics\plant_genes.dm" -#include "code\modules\hydroponics\sample.dm" -#include "code\modules\hydroponics\seed_extractor.dm" -#include "code\modules\hydroponics\seeds.dm" -#include "code\modules\hydroponics\beekeeping\beebox.dm" -#include "code\modules\hydroponics\beekeeping\beekeeper_suit.dm" -#include "code\modules\hydroponics\beekeeping\honey_frame.dm" -#include "code\modules\hydroponics\beekeeping\honeycomb.dm" -#include "code\modules\hydroponics\grown\ambrosia.dm" -#include "code\modules\hydroponics\grown\apple.dm" -#include "code\modules\hydroponics\grown\banana.dm" -#include "code\modules\hydroponics\grown\beans.dm" -#include "code\modules\hydroponics\grown\berries.dm" -#include "code\modules\hydroponics\grown\cannabis.dm" -#include "code\modules\hydroponics\grown\cereals.dm" -#include "code\modules\hydroponics\grown\chili.dm" -#include "code\modules\hydroponics\grown\citrus.dm" -#include "code\modules\hydroponics\grown\cocoa_vanilla.dm" -#include "code\modules\hydroponics\grown\corn.dm" -#include "code\modules\hydroponics\grown\cotton.dm" -#include "code\modules\hydroponics\grown\eggplant.dm" -#include "code\modules\hydroponics\grown\flowers.dm" -#include "code\modules\hydroponics\grown\garlic.dm" -#include "code\modules\hydroponics\grown\grass_carpet.dm" -#include "code\modules\hydroponics\grown\kudzu.dm" -#include "code\modules\hydroponics\grown\melon.dm" -#include "code\modules\hydroponics\grown\misc.dm" -#include "code\modules\hydroponics\grown\mushrooms.dm" -#include "code\modules\hydroponics\grown\nettle.dm" -#include "code\modules\hydroponics\grown\onion.dm" -#include "code\modules\hydroponics\grown\peach.dm" -#include "code\modules\hydroponics\grown\peanuts.dm" -#include "code\modules\hydroponics\grown\pineapple.dm" -#include "code\modules\hydroponics\grown\potato.dm" -#include "code\modules\hydroponics\grown\pumpkin.dm" -#include "code\modules\hydroponics\grown\random.dm" -#include "code\modules\hydroponics\grown\replicapod.dm" -#include "code\modules\hydroponics\grown\root.dm" -#include "code\modules\hydroponics\grown\tea_coffee.dm" -#include "code\modules\hydroponics\grown\tobacco.dm" -#include "code\modules\hydroponics\grown\tomato.dm" -#include "code\modules\hydroponics\grown\towercap.dm" -#include "code\modules\integrated_electronics\_defines.dm" -#include "code\modules\integrated_electronics\core\analyzer.dm" -#include "code\modules\integrated_electronics\core\assemblies.dm" -#include "code\modules\integrated_electronics\core\debugger.dm" -#include "code\modules\integrated_electronics\core\detailer.dm" -#include "code\modules\integrated_electronics\core\helpers.dm" -#include "code\modules\integrated_electronics\core\integrated_circuit.dm" -#include "code\modules\integrated_electronics\core\pins.dm" -#include "code\modules\integrated_electronics\core\printer.dm" -#include "code\modules\integrated_electronics\core\saved_circuits.dm" -#include "code\modules\integrated_electronics\core\wirer.dm" -#include "code\modules\integrated_electronics\core\special_pins\boolean_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\char_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\color_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\dir_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\index_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\list_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\number_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\ref_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\selfref_pin.dm" -#include "code\modules\integrated_electronics\core\special_pins\string_pin.dm" -#include "code\modules\integrated_electronics\passive\passive.dm" -#include "code\modules\integrated_electronics\passive\power.dm" -#include "code\modules\integrated_electronics\subtypes\access.dm" -#include "code\modules\integrated_electronics\subtypes\arithmetic.dm" -#include "code\modules\integrated_electronics\subtypes\atmospherics.dm" -#include "code\modules\integrated_electronics\subtypes\converters.dm" -#include "code\modules\integrated_electronics\subtypes\data_transfer.dm" -#include "code\modules\integrated_electronics\subtypes\input.dm" -#include "code\modules\integrated_electronics\subtypes\lists.dm" -#include "code\modules\integrated_electronics\subtypes\logic.dm" -#include "code\modules\integrated_electronics\subtypes\manipulation.dm" -#include "code\modules\integrated_electronics\subtypes\memory.dm" -#include "code\modules\integrated_electronics\subtypes\output.dm" -#include "code\modules\integrated_electronics\subtypes\power.dm" -#include "code\modules\integrated_electronics\subtypes\reagents.dm" -#include "code\modules\integrated_electronics\subtypes\smart.dm" -#include "code\modules\integrated_electronics\subtypes\text.dm" -#include "code\modules\integrated_electronics\subtypes\time.dm" -#include "code\modules\integrated_electronics\subtypes\trig.dm" -#include "code\modules\integrated_electronics\subtypes\weaponized.dm" -#include "code\modules\jobs\access.dm" -#include "code\modules\jobs\job_exp.dm" -#include "code\modules\jobs\jobs.dm" -#include "code\modules\jobs\job_types\assistant.dm" -#include "code\modules\jobs\job_types\captain.dm" -#include "code\modules\jobs\job_types\cargo_service.dm" -#include "code\modules\jobs\job_types\civilian.dm" -#include "code\modules\jobs\job_types\civilian_chaplain.dm" -#include "code\modules\jobs\job_types\engineering.dm" -#include "code\modules\jobs\job_types\job.dm" -#include "code\modules\jobs\job_types\job_alt_titles.dm" -#include "code\modules\jobs\job_types\medical.dm" -#include "code\modules\jobs\job_types\science.dm" -#include "code\modules\jobs\job_types\security.dm" -#include "code\modules\jobs\job_types\silicon.dm" -#include "code\modules\jobs\map_changes\map_changes.dm" -#include "code\modules\keybindings\bindings_admin.dm" -#include "code\modules\keybindings\bindings_atom.dm" -#include "code\modules\keybindings\bindings_carbon.dm" -#include "code\modules\keybindings\bindings_client.dm" -#include "code\modules\keybindings\bindings_human.dm" -#include "code\modules\keybindings\bindings_living.dm" -#include "code\modules\keybindings\bindings_mob.dm" -#include "code\modules\keybindings\bindings_robot.dm" -#include "code\modules\keybindings\focus.dm" -#include "code\modules\keybindings\setup.dm" -#include "code\modules\language\aphasia.dm" -#include "code\modules\language\beachbum.dm" -#include "code\modules\language\codespeak.dm" -#include "code\modules\language\common.dm" -#include "code\modules\language\draconic.dm" -#include "code\modules\language\drone.dm" -#include "code\modules\language\language.dm" -#include "code\modules\language\language_holder.dm" -#include "code\modules\language\language_menu.dm" -#include "code\modules\language\machine.dm" -#include "code\modules\language\monkey.dm" -#include "code\modules\language\mushroom.dm" -#include "code\modules\language\narsian.dm" -#include "code\modules\language\ratvarian.dm" -#include "code\modules\language\slime.dm" -#include "code\modules\language\swarmer.dm" -#include "code\modules\language\vampiric.dm" -#include "code\modules\language\xenocommon.dm" -#include "code\modules\library\lib_codex_gigas.dm" -#include "code\modules\library\lib_items.dm" -#include "code\modules\library\lib_machines.dm" -#include "code\modules\library\random_books.dm" -#include "code\modules\library\soapstone.dm" -#include "code\modules\lighting\lighting_area.dm" -#include "code\modules\lighting\lighting_atom.dm" -#include "code\modules\lighting\lighting_corner.dm" -#include "code\modules\lighting\lighting_object.dm" -#include "code\modules\lighting\lighting_setup.dm" -#include "code\modules\lighting\lighting_source.dm" -#include "code\modules\lighting\lighting_turf.dm" -#include "code\modules\mapping\dmm_suite.dm" -#include "code\modules\mapping\map_template.dm" -#include "code\modules\mapping\mapping_helpers.dm" -#include "code\modules\mapping\preloader.dm" -#include "code\modules\mapping\reader.dm" -#include "code\modules\mapping\ruins.dm" -#include "code\modules\mapping\verify.dm" -#include "code\modules\mapping\space_management\multiz_helpers.dm" -#include "code\modules\mapping\space_management\space_level.dm" -#include "code\modules\mapping\space_management\space_reservation.dm" -#include "code\modules\mapping\space_management\space_transition.dm" -#include "code\modules\mapping\space_management\traits.dm" -#include "code\modules\mapping\space_management\zlevel_manager.dm" -#include "code\modules\mining\abandoned_crates.dm" -#include "code\modules\mining\aux_base.dm" -#include "code\modules\mining\aux_base_camera.dm" -#include "code\modules\mining\fulton.dm" -#include "code\modules\mining\machine_processing.dm" -#include "code\modules\mining\machine_redemption.dm" -#include "code\modules\mining\machine_silo.dm" -#include "code\modules\mining\machine_stacking.dm" -#include "code\modules\mining\machine_unloading.dm" -#include "code\modules\mining\machine_vending.dm" -#include "code\modules\mining\mine_items.dm" -#include "code\modules\mining\minebot.dm" -#include "code\modules\mining\mint.dm" -#include "code\modules\mining\money_bag.dm" -#include "code\modules\mining\ores_coins.dm" -#include "code\modules\mining\satchel_ore_boxdm.dm" -#include "code\modules\mining\shelters.dm" -#include "code\modules\mining\equipment\explorer_gear.dm" -#include "code\modules\mining\equipment\goliath_hide.dm" -#include "code\modules\mining\equipment\kinetic_crusher.dm" -#include "code\modules\mining\equipment\lazarus_injector.dm" -#include "code\modules\mining\equipment\marker_beacons.dm" -#include "code\modules\mining\equipment\mineral_scanner.dm" -#include "code\modules\mining\equipment\mining_tools.dm" -#include "code\modules\mining\equipment\regenerative_core.dm" -#include "code\modules\mining\equipment\resonator.dm" -#include "code\modules\mining\equipment\survival_pod.dm" -#include "code\modules\mining\equipment\vendor_items.dm" -#include "code\modules\mining\equipment\wormhole_jaunter.dm" -#include "code\modules\mining\laborcamp\laborshuttle.dm" -#include "code\modules\mining\laborcamp\laborstacker.dm" -#include "code\modules\mining\lavaland\ash_flora.dm" -#include "code\modules\mining\lavaland\necropolis_chests.dm" -#include "code\modules\mining\lavaland\ruins\gym.dm" -#include "code\modules\mob\death.dm" -#include "code\modules\mob\emote.dm" -#include "code\modules\mob\inventory.dm" -#include "code\modules\mob\login.dm" -#include "code\modules\mob\logout.dm" -#include "code\modules\mob\mob.dm" -#include "code\modules\mob\mob_defines.dm" -#include "code\modules\mob\mob_helpers.dm" -#include "code\modules\mob\mob_movement.dm" -#include "code\modules\mob\mob_movespeed.dm" -#include "code\modules\mob\mob_transformation_simple.dm" -#include "code\modules\mob\say.dm" -#include "code\modules\mob\say_vr.dm" -#include "code\modules\mob\status_procs.dm" -#include "code\modules\mob\transform_procs.dm" -#include "code\modules\mob\typing_indicator.dm" -#include "code\modules\mob\update_icons.dm" -#include "code\modules\mob\camera\camera.dm" -#include "code\modules\mob\dead\dead.dm" -#include "code\modules\mob\dead\new_player\login.dm" -#include "code\modules\mob\dead\new_player\logout.dm" -#include "code\modules\mob\dead\new_player\new_player.dm" -#include "code\modules\mob\dead\new_player\poll.dm" -#include "code\modules\mob\dead\new_player\preferences_setup.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\_sprite_accessories.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\body_markings.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\caps.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\ears.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\frills.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\hair_face.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\hair_head.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\horns.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\legs.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\moth_fluff.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\moth_wings.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\pines.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\snouts.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\socks.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\tails.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\undershirt.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\underwear.dm" -#include "code\modules\mob\dead\new_player\sprite_accessories\wings.dm" -#include "code\modules\mob\dead\observer\login.dm" -#include "code\modules\mob\dead\observer\logout.dm" -#include "code\modules\mob\dead\observer\notificationprefs.dm" -#include "code\modules\mob\dead\observer\observer.dm" -#include "code\modules\mob\dead\observer\observer_movement.dm" -#include "code\modules\mob\dead\observer\say.dm" -#include "code\modules\mob\living\blood.dm" -#include "code\modules\mob\living\bloodcrawl.dm" -#include "code\modules\mob\living\damage_procs.dm" -#include "code\modules\mob\living\death.dm" -#include "code\modules\mob\living\emote.dm" -#include "code\modules\mob\living\life.dm" -#include "code\modules\mob\living\living.dm" -#include "code\modules\mob\living\living_defense.dm" -#include "code\modules\mob\living\living_defines.dm" -#include "code\modules\mob\living\living_movement.dm" -#include "code\modules\mob\living\login.dm" -#include "code\modules\mob\living\logout.dm" -#include "code\modules\mob\living\say.dm" -#include "code\modules\mob\living\status_procs.dm" -#include "code\modules\mob\living\taste.dm" -#include "code\modules\mob\living\ventcrawling.dm" -#include "code\modules\mob\living\brain\brain.dm" -#include "code\modules\mob\living\brain\brain_item.dm" -#include "code\modules\mob\living\brain\death.dm" -#include "code\modules\mob\living\brain\emote.dm" -#include "code\modules\mob\living\brain\life.dm" -#include "code\modules\mob\living\brain\MMI.dm" -#include "code\modules\mob\living\brain\posibrain.dm" -#include "code\modules\mob\living\brain\say.dm" -#include "code\modules\mob\living\brain\status_procs.dm" -#include "code\modules\mob\living\carbon\carbon.dm" -#include "code\modules\mob\living\carbon\carbon_defense.dm" -#include "code\modules\mob\living\carbon\carbon_defines.dm" -#include "code\modules\mob\living\carbon\carbon_movement.dm" -#include "code\modules\mob\living\carbon\damage_procs.dm" -#include "code\modules\mob\living\carbon\death.dm" -#include "code\modules\mob\living\carbon\emote.dm" -#include "code\modules\mob\living\carbon\examine.dm" -#include "code\modules\mob\living\carbon\give.dm" -#include "code\modules\mob\living\carbon\inventory.dm" -#include "code\modules\mob\living\carbon\life.dm" -#include "code\modules\mob\living\carbon\say.dm" -#include "code\modules\mob\living\carbon\status_procs.dm" -#include "code\modules\mob\living\carbon\update_icons.dm" -#include "code\modules\mob\living\carbon\alien\alien.dm" -#include "code\modules\mob\living\carbon\alien\alien_defense.dm" -#include "code\modules\mob\living\carbon\alien\damage_procs.dm" -#include "code\modules\mob\living\carbon\alien\death.dm" -#include "code\modules\mob\living\carbon\alien\emote.dm" -#include "code\modules\mob\living\carbon\alien\life.dm" -#include "code\modules\mob\living\carbon\alien\login.dm" -#include "code\modules\mob\living\carbon\alien\logout.dm" -#include "code\modules\mob\living\carbon\alien\organs.dm" -#include "code\modules\mob\living\carbon\alien\say.dm" -#include "code\modules\mob\living\carbon\alien\screen.dm" -#include "code\modules\mob\living\carbon\alien\status_procs.dm" -#include "code\modules\mob\living\carbon\alien\update_icons.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\alien_powers.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\death.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\humanoid.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\humanoid_defense.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\inventory.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\life.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\queen.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\update_icons.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\caste\drone.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\caste\hunter.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\caste\praetorian.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\caste\sentinel.dm" -#include "code\modules\mob\living\carbon\alien\larva\death.dm" -#include "code\modules\mob\living\carbon\alien\larva\inventory.dm" -#include "code\modules\mob\living\carbon\alien\larva\larva.dm" -#include "code\modules\mob\living\carbon\alien\larva\larva_defense.dm" -#include "code\modules\mob\living\carbon\alien\larva\life.dm" -#include "code\modules\mob\living\carbon\alien\larva\powers.dm" -#include "code\modules\mob\living\carbon\alien\larva\update_icons.dm" -#include "code\modules\mob\living\carbon\alien\special\alien_embryo.dm" -#include "code\modules\mob\living\carbon\alien\special\facehugger.dm" -#include "code\modules\mob\living\carbon\human\damage_procs.dm" -#include "code\modules\mob\living\carbon\human\death.dm" -#include "code\modules\mob\living\carbon\human\dummy.dm" -#include "code\modules\mob\living\carbon\human\emote.dm" -#include "code\modules\mob\living\carbon\human\examine.dm" -#include "code\modules\mob\living\carbon\human\examine_vr.dm" -#include "code\modules\mob\living\carbon\human\human.dm" -#include "code\modules\mob\living\carbon\human\human_defense.dm" -#include "code\modules\mob\living\carbon\human\human_defines.dm" -#include "code\modules\mob\living\carbon\human\human_helpers.dm" -#include "code\modules\mob\living\carbon\human\human_movement.dm" -#include "code\modules\mob\living\carbon\human\inventory.dm" -#include "code\modules\mob\living\carbon\human\life.dm" -#include "code\modules\mob\living\carbon\human\physiology.dm" -#include "code\modules\mob\living\carbon\human\say.dm" -#include "code\modules\mob\living\carbon\human\species.dm" -#include "code\modules\mob\living\carbon\human\status_procs.dm" -#include "code\modules\mob\living\carbon\human\typing_indicator.dm" -#include "code\modules\mob\living\carbon\human\update_icons.dm" -#include "code\modules\mob\living\carbon\human\species_types\abductors.dm" -#include "code\modules\mob\living\carbon\human\species_types\android.dm" -#include "code\modules\mob\living\carbon\human\species_types\angel.dm" -#include "code\modules\mob\living\carbon\human\species_types\corporate.dm" -#include "code\modules\mob\living\carbon\human\species_types\dullahan.dm" -#include "code\modules\mob\living\carbon\human\species_types\felinid.dm" -#include "code\modules\mob\living\carbon\human\species_types\flypeople.dm" -#include "code\modules\mob\living\carbon\human\species_types\golems.dm" -#include "code\modules\mob\living\carbon\human\species_types\humans.dm" -#include "code\modules\mob\living\carbon\human\species_types\jellypeople.dm" -#include "code\modules\mob\living\carbon\human\species_types\lizardpeople.dm" -#include "code\modules\mob\living\carbon\human\species_types\mothmen.dm" -#include "code\modules\mob\living\carbon\human\species_types\mushpeople.dm" -#include "code\modules\mob\living\carbon\human\species_types\plasmamen.dm" -#include "code\modules\mob\living\carbon\human\species_types\podpeople.dm" -#include "code\modules\mob\living\carbon\human\species_types\shadowpeople.dm" -#include "code\modules\mob\living\carbon\human\species_types\skeletons.dm" -#include "code\modules\mob\living\carbon\human\species_types\synths.dm" -#include "code\modules\mob\living\carbon\human\species_types\vampire.dm" -#include "code\modules\mob\living\carbon\human\species_types\zombies.dm" -#include "code\modules\mob\living\carbon\monkey\combat.dm" -#include "code\modules\mob\living\carbon\monkey\death.dm" -#include "code\modules\mob\living\carbon\monkey\inventory.dm" -#include "code\modules\mob\living\carbon\monkey\life.dm" -#include "code\modules\mob\living\carbon\monkey\monkey.dm" -#include "code\modules\mob\living\carbon\monkey\monkey_defense.dm" -#include "code\modules\mob\living\carbon\monkey\punpun.dm" -#include "code\modules\mob\living\carbon\monkey\update_icons.dm" -#include "code\modules\mob\living\silicon\damage_procs.dm" -#include "code\modules\mob\living\silicon\death.dm" -#include "code\modules\mob\living\silicon\examine.dm" -#include "code\modules\mob\living\silicon\laws.dm" -#include "code\modules\mob\living\silicon\login.dm" -#include "code\modules\mob\living\silicon\say.dm" -#include "code\modules\mob\living\silicon\silicon.dm" -#include "code\modules\mob\living\silicon\silicon_defense.dm" -#include "code\modules\mob\living\silicon\silicon_movement.dm" -#include "code\modules\mob\living\silicon\ai\ai.dm" -#include "code\modules\mob\living\silicon\ai\ai_defense.dm" -#include "code\modules\mob\living\silicon\ai\death.dm" -#include "code\modules\mob\living\silicon\ai\examine.dm" -#include "code\modules\mob\living\silicon\ai\laws.dm" -#include "code\modules\mob\living\silicon\ai\life.dm" -#include "code\modules\mob\living\silicon\ai\login.dm" -#include "code\modules\mob\living\silicon\ai\logout.dm" -#include "code\modules\mob\living\silicon\ai\multicam.dm" -#include "code\modules\mob\living\silicon\ai\say.dm" -#include "code\modules\mob\living\silicon\ai\vox_sounds.dm" -#include "code\modules\mob\living\silicon\ai\freelook\cameranet.dm" -#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm" -#include "code\modules\mob\living\silicon\ai\freelook\eye.dm" -#include "code\modules\mob\living\silicon\ai\freelook\read_me.dm" -#include "code\modules\mob\living\silicon\pai\death.dm" -#include "code\modules\mob\living\silicon\pai\pai.dm" -#include "code\modules\mob\living\silicon\pai\pai_defense.dm" -#include "code\modules\mob\living\silicon\pai\pai_shell.dm" -#include "code\modules\mob\living\silicon\pai\personality.dm" -#include "code\modules\mob\living\silicon\pai\say.dm" -#include "code\modules\mob\living\silicon\pai\software.dm" -#include "code\modules\mob\living\silicon\robot\death.dm" -#include "code\modules\mob\living\silicon\robot\emote.dm" -#include "code\modules\mob\living\silicon\robot\examine.dm" -#include "code\modules\mob\living\silicon\robot\inventory.dm" -#include "code\modules\mob\living\silicon\robot\laws.dm" -#include "code\modules\mob\living\silicon\robot\life.dm" -#include "code\modules\mob\living\silicon\robot\login.dm" -#include "code\modules\mob\living\silicon\robot\robot.dm" -#include "code\modules\mob\living\silicon\robot\robot_defense.dm" -#include "code\modules\mob\living\silicon\robot\robot_modules.dm" -#include "code\modules\mob\living\silicon\robot\robot_movement.dm" -#include "code\modules\mob\living\silicon\robot\say.dm" -#include "code\modules\mob\living\simple_animal\animal_defense.dm" -#include "code\modules\mob\living\simple_animal\astral.dm" -#include "code\modules\mob\living\simple_animal\constructs.dm" -#include "code\modules\mob\living\simple_animal\corpse.dm" -#include "code\modules\mob\living\simple_animal\damage_procs.dm" -#include "code\modules\mob\living\simple_animal\parrot.dm" -#include "code\modules\mob\living\simple_animal\shade.dm" -#include "code\modules\mob\living\simple_animal\simple_animal.dm" -#include "code\modules\mob\living\simple_animal\simple_animal_vr.dm" -#include "code\modules\mob\living\simple_animal\status_procs.dm" -#include "code\modules\mob\living\simple_animal\bot\bot.dm" -#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm" -#include "code\modules\mob\living\simple_animal\bot\construction.dm" -#include "code\modules\mob\living\simple_animal\bot\ed209bot.dm" -#include "code\modules\mob\living\simple_animal\bot\firebot.dm" -#include "code\modules\mob\living\simple_animal\bot\floorbot.dm" -#include "code\modules\mob\living\simple_animal\bot\honkbot.dm" -#include "code\modules\mob\living\simple_animal\bot\medbot.dm" -#include "code\modules\mob\living\simple_animal\bot\mulebot.dm" -#include "code\modules\mob\living\simple_animal\bot\secbot.dm" -#include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm" -#include "code\modules\mob\living\simple_animal\friendly\butterfly.dm" -#include "code\modules\mob\living\simple_animal\friendly\cat.dm" -#include "code\modules\mob\living\simple_animal\friendly\cockroach.dm" -#include "code\modules\mob\living\simple_animal\friendly\crab.dm" -#include "code\modules\mob\living\simple_animal\friendly\dog.dm" -#include "code\modules\mob\living\simple_animal\friendly\farm_animals.dm" -#include "code\modules\mob\living\simple_animal\friendly\fox.dm" -#include "code\modules\mob\living\simple_animal\friendly\gondola.dm" -#include "code\modules\mob\living\simple_animal\friendly\lizard.dm" -#include "code\modules\mob\living\simple_animal\friendly\mouse.dm" -#include "code\modules\mob\living\simple_animal\friendly\panda.dm" -#include "code\modules\mob\living\simple_animal\friendly\penguin.dm" -#include "code\modules\mob\living\simple_animal\friendly\pet.dm" -#include "code\modules\mob\living\simple_animal\friendly\sloth.dm" -#include "code\modules\mob\living\simple_animal\friendly\snake.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\_drone.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\drones_as_items.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\extra_drone_types.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\interaction.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\inventory.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\say.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\verbs.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\visuals_icons.dm" -#include "code\modules\mob\living\simple_animal\guardian\guardian.dm" -#include "code\modules\mob\living\simple_animal\guardian\guardiannaming.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\assassin.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\charger.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\dextrous.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\explosive.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\fire.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\lightning.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\protector.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\ranged.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\standard.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\support.dm" -#include "code\modules\mob\living\simple_animal\hostile\alien.dm" -#include "code\modules\mob\living\simple_animal\hostile\bear.dm" -#include "code\modules\mob\living\simple_animal\hostile\bees.dm" -#include "code\modules\mob\living\simple_animal\hostile\carp.dm" -#include "code\modules\mob\living\simple_animal\hostile\cat_butcher.dm" -#include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" -#include "code\modules\mob\living\simple_animal\hostile\faithless.dm" -#include "code\modules\mob\living\simple_animal\hostile\floor_cluwne.dm" -#include "code\modules\mob\living\simple_animal\hostile\giant_spider.dm" -#include "code\modules\mob\living\simple_animal\hostile\goose.dm" -#include "code\modules\mob\living\simple_animal\hostile\headcrab.dm" -#include "code\modules\mob\living\simple_animal\hostile\hivebot.dm" -#include "code\modules\mob\living\simple_animal\hostile\hostile.dm" -#include "code\modules\mob\living\simple_animal\hostile\illusion.dm" -#include "code\modules\mob\living\simple_animal\hostile\killertomato.dm" -#include "code\modules\mob\living\simple_animal\hostile\mecha_pilot.dm" -#include "code\modules\mob\living\simple_animal\hostile\mimic.dm" -#include "code\modules\mob\living\simple_animal\hostile\mushroom.dm" -#include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm" -#include "code\modules\mob\living\simple_animal\hostile\netherworld.dm" -#include "code\modules\mob\living\simple_animal\hostile\pirate.dm" -#include "code\modules\mob\living\simple_animal\hostile\russian.dm" -#include "code\modules\mob\living\simple_animal\hostile\sharks.dm" -#include "code\modules\mob\living\simple_animal\hostile\skeleton.dm" -#include "code\modules\mob\living\simple_animal\hostile\statue.dm" -#include "code\modules\mob\living\simple_animal\hostile\stickman.dm" -#include "code\modules\mob\living\simple_animal\hostile\syndicate.dm" -#include "code\modules\mob\living\simple_animal\hostile\tree.dm" -#include "code\modules\mob\living\simple_animal\hostile\venus_human_trap.dm" -#include "code\modules\mob\living\simple_animal\hostile\wizard.dm" -#include "code\modules\mob\living\simple_animal\hostile\wumborian_fugu.dm" -#include "code\modules\mob\living\simple_animal\hostile\zombie.dm" -#include "code\modules\mob\living\simple_animal\hostile\bosses\boss.dm" -#include "code\modules\mob\living\simple_animal\hostile\bosses\paperwizard.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\emotes.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\gorilla.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\visuals_icons.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\mega_arachnid.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\seedling.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\colossus.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\dragon_vore.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\drake.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\megafauna.dm" -#include "code\modules\mob\living\simple_animal\hostile\megafauna\swarmer.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\basilisk.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\goldgrub.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\goliath.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\hivelord.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\bat.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\frog.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\ghost.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\spaceman.dm" -#include "code\modules\mob\living\simple_animal\slime\death.dm" -#include "code\modules\mob\living\simple_animal\slime\emote.dm" -#include "code\modules\mob\living\simple_animal\slime\life.dm" -#include "code\modules\mob\living\simple_animal\slime\powers.dm" -#include "code\modules\mob\living\simple_animal\slime\say.dm" -#include "code\modules\mob\living\simple_animal\slime\slime.dm" -#include "code\modules\mob\living\simple_animal\slime\subtypes.dm" -#include "code\modules\modular_computers\laptop_vendor.dm" -#include "code\modules\modular_computers\computers\item\computer.dm" -#include "code\modules\modular_computers\computers\item\computer_components.dm" -#include "code\modules\modular_computers\computers\item\computer_damage.dm" -#include "code\modules\modular_computers\computers\item\computer_power.dm" -#include "code\modules\modular_computers\computers\item\computer_ui.dm" -#include "code\modules\modular_computers\computers\item\laptop.dm" -#include "code\modules\modular_computers\computers\item\laptop_presets.dm" -#include "code\modules\modular_computers\computers\item\processor.dm" -#include "code\modules\modular_computers\computers\item\tablet.dm" -#include "code\modules\modular_computers\computers\item\tablet_presets.dm" -#include "code\modules\modular_computers\computers\machinery\console_presets.dm" -#include "code\modules\modular_computers\computers\machinery\modular_computer.dm" -#include "code\modules\modular_computers\computers\machinery\modular_console.dm" -#include "code\modules\modular_computers\file_system\computer_file.dm" -#include "code\modules\modular_computers\file_system\data.dm" -#include "code\modules\modular_computers\file_system\program.dm" -#include "code\modules\modular_computers\file_system\program_events.dm" -#include "code\modules\modular_computers\file_system\programs\airestorer.dm" -#include "code\modules\modular_computers\file_system\programs\alarm.dm" -#include "code\modules\modular_computers\file_system\programs\card.dm" -#include "code\modules\modular_computers\file_system\programs\configurator.dm" -#include "code\modules\modular_computers\file_system\programs\file_browser.dm" -#include "code\modules\modular_computers\file_system\programs\ntdownloader.dm" -#include "code\modules\modular_computers\file_system\programs\ntmonitor.dm" -#include "code\modules\modular_computers\file_system\programs\ntnrc_client.dm" -#include "code\modules\modular_computers\file_system\programs\nttransfer.dm" -#include "code\modules\modular_computers\file_system\programs\powermonitor.dm" -#include "code\modules\modular_computers\file_system\programs\sm_monitor.dm" -#include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm" -#include "code\modules\modular_computers\file_system\programs\antagonist\revelation.dm" -#include "code\modules\modular_computers\hardware\_hardware.dm" -#include "code\modules\modular_computers\hardware\ai_slot.dm" -#include "code\modules\modular_computers\hardware\battery_module.dm" -#include "code\modules\modular_computers\hardware\card_slot.dm" -#include "code\modules\modular_computers\hardware\CPU.dm" -#include "code\modules\modular_computers\hardware\hard_drive.dm" -#include "code\modules\modular_computers\hardware\network_card.dm" -#include "code\modules\modular_computers\hardware\portable_disk.dm" -#include "code\modules\modular_computers\hardware\printer.dm" -#include "code\modules\modular_computers\hardware\recharger.dm" -#include "code\modules\modular_computers\NTNet\NTNRC\conversation.dm" -#include "code\modules\ninja\__ninjaDefines.dm" -#include "code\modules\ninja\energy_katana.dm" -#include "code\modules\ninja\ninja_event.dm" -#include "code\modules\ninja\outfit.dm" -#include "code\modules\ninja\suit\gloves.dm" -#include "code\modules\ninja\suit\head.dm" -#include "code\modules\ninja\suit\mask.dm" -#include "code\modules\ninja\suit\ninjaDrainAct.dm" -#include "code\modules\ninja\suit\shoes.dm" -#include "code\modules\ninja\suit\suit.dm" -#include "code\modules\ninja\suit\suit_attackby.dm" -#include "code\modules\ninja\suit\suit_initialisation.dm" -#include "code\modules\ninja\suit\suit_process.dm" -#include "code\modules\ninja\suit\n_suit_verbs\energy_net_nets.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_adrenaline.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_cost_check.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_empulse.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_net.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_smoke.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_stars.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_stealth.dm" -#include "code\modules\ninja\suit\n_suit_verbs\ninja_sword_recall.dm" -#include "code\modules\NTNet\netdata.dm" -#include "code\modules\NTNet\network.dm" -#include "code\modules\NTNet\relays.dm" -#include "code\modules\NTNet\services\_service.dm" -#include "code\modules\oracle_ui\assets.dm" -#include "code\modules\oracle_ui\hookup_procs.dm" -#include "code\modules\oracle_ui\oracle_ui.dm" -#include "code\modules\oracle_ui\themed.dm" -#include "code\modules\paperwork\clipboard.dm" -#include "code\modules\paperwork\contract.dm" -#include "code\modules\paperwork\filingcabinet.dm" -#include "code\modules\paperwork\folders.dm" -#include "code\modules\paperwork\handlabeler.dm" -#include "code\modules\paperwork\paper.dm" -#include "code\modules\paperwork\paper_cutter.dm" -#include "code\modules\paperwork\paper_premade.dm" -#include "code\modules\paperwork\paperbin.dm" -#include "code\modules\paperwork\paperplane.dm" -#include "code\modules\paperwork\pen.dm" -#include "code\modules\paperwork\photocopier.dm" -#include "code\modules\paperwork\stamps.dm" -#include "code\modules\photography\_pictures.dm" -#include "code\modules\photography\camera\camera.dm" -#include "code\modules\photography\camera\camera_image_capturing.dm" -#include "code\modules\photography\camera\film.dm" -#include "code\modules\photography\camera\other.dm" -#include "code\modules\photography\camera\silicon_camera.dm" -#include "code\modules\photography\photos\album.dm" -#include "code\modules\photography\photos\frame.dm" -#include "code\modules\photography\photos\photo.dm" -#include "code\modules\pool\pool_controller.dm" -#include "code\modules\pool\pool_drain.dm" -#include "code\modules\pool\pool_effects.dm" -#include "code\modules\pool\pool_main.dm" -#include "code\modules\pool\pool_noodles.dm" -#include "code\modules\pool\pool_structures.dm" -#include "code\modules\pool\pool_wires.dm" -#include "code\modules\power\apc.dm" -#include "code\modules\power\cable.dm" -#include "code\modules\power\cell.dm" -#include "code\modules\power\floodlight.dm" -#include "code\modules\power\generator.dm" -#include "code\modules\power\gravitygenerator.dm" -#include "code\modules\power\lighting.dm" -#include "code\modules\power\monitor.dm" -#include "code\modules\power\multiz.dm" -#include "code\modules\power\port_gen.dm" -#include "code\modules\power\power.dm" -#include "code\modules\power\powernet.dm" -#include "code\modules\power\rtg.dm" -#include "code\modules\power\smes.dm" -#include "code\modules\power\solar.dm" -#include "code\modules\power\terminal.dm" -#include "code\modules\power\tracker.dm" -#include "code\modules\power\turbine.dm" -#include "code\modules\power\antimatter\containment_jar.dm" -#include "code\modules\power\antimatter\control.dm" -#include "code\modules\power\antimatter\shielding.dm" -#include "code\modules\power\singularity\collector.dm" -#include "code\modules\power\singularity\containment_field.dm" -#include "code\modules\power\singularity\emitter.dm" -#include "code\modules\power\singularity\field_generator.dm" -#include "code\modules\power\singularity\generator.dm" -#include "code\modules\power\singularity\investigate.dm" -#include "code\modules\power\singularity\narsie.dm" -#include "code\modules\power\singularity\singularity.dm" -#include "code\modules\power\singularity\particle_accelerator\particle.dm" -#include "code\modules\power\singularity\particle_accelerator\particle_accelerator.dm" -#include "code\modules\power\singularity\particle_accelerator\particle_control.dm" -#include "code\modules\power\singularity\particle_accelerator\particle_emitter.dm" -#include "code\modules\power\supermatter\supermatter.dm" -#include "code\modules\power\tesla\coil.dm" -#include "code\modules\power\tesla\energy_ball.dm" -#include "code\modules\power\tesla\generator.dm" -#include "code\modules\procedural_mapping\mapGenerator.dm" -#include "code\modules\procedural_mapping\mapGeneratorModule.dm" -#include "code\modules\procedural_mapping\mapGeneratorObj.dm" -#include "code\modules\procedural_mapping\mapGeneratorReadme.dm" -#include "code\modules\procedural_mapping\mapGeneratorModules\helpers.dm" -#include "code\modules\procedural_mapping\mapGeneratorModules\nature.dm" -#include "code\modules\procedural_mapping\mapGenerators\asteroid.dm" -#include "code\modules\procedural_mapping\mapGenerators\cellular.dm" -#include "code\modules\procedural_mapping\mapGenerators\cult.dm" -#include "code\modules\procedural_mapping\mapGenerators\lava_river.dm" -#include "code\modules\procedural_mapping\mapGenerators\lavaland.dm" -#include "code\modules\procedural_mapping\mapGenerators\nature.dm" -#include "code\modules\procedural_mapping\mapGenerators\repair.dm" -#include "code\modules\procedural_mapping\mapGenerators\shuttle.dm" -#include "code\modules\procedural_mapping\mapGenerators\syndicate.dm" -#include "code\modules\projectiles\gun.dm" -#include "code\modules\projectiles\pins.dm" -#include "code\modules\projectiles\projectile.dm" -#include "code\modules\projectiles\ammunition\_ammunition.dm" -#include "code\modules\projectiles\ammunition\_firing.dm" -#include "code\modules\projectiles\ammunition\ballistic\lmg.dm" -#include "code\modules\projectiles\ammunition\ballistic\pistol.dm" -#include "code\modules\projectiles\ammunition\ballistic\revolver.dm" -#include "code\modules\projectiles\ammunition\ballistic\rifle.dm" -#include "code\modules\projectiles\ammunition\ballistic\shotgun.dm" -#include "code\modules\projectiles\ammunition\ballistic\smg.dm" -#include "code\modules\projectiles\ammunition\ballistic\sniper.dm" -#include "code\modules\projectiles\ammunition\caseless\_caseless.dm" -#include "code\modules\projectiles\ammunition\caseless\foam.dm" -#include "code\modules\projectiles\ammunition\caseless\misc.dm" -#include "code\modules\projectiles\ammunition\caseless\rocket.dm" -#include "code\modules\projectiles\ammunition\energy\_energy.dm" -#include "code\modules\projectiles\ammunition\energy\ebow.dm" -#include "code\modules\projectiles\ammunition\energy\gravity.dm" -#include "code\modules\projectiles\ammunition\energy\laser.dm" -#include "code\modules\projectiles\ammunition\energy\lmg.dm" -#include "code\modules\projectiles\ammunition\energy\plasma.dm" -#include "code\modules\projectiles\ammunition\energy\plasma_cit.dm" -#include "code\modules\projectiles\ammunition\energy\portal.dm" -#include "code\modules\projectiles\ammunition\energy\special.dm" -#include "code\modules\projectiles\ammunition\energy\stun.dm" -#include "code\modules\projectiles\ammunition\special\magic.dm" -#include "code\modules\projectiles\ammunition\special\syringe.dm" -#include "code\modules\projectiles\boxes_magazines\_box_magazine.dm" -#include "code\modules\projectiles\boxes_magazines\ammo_boxes.dm" -#include "code\modules\projectiles\boxes_magazines\external\grenade.dm" -#include "code\modules\projectiles\boxes_magazines\external\lmg.dm" -#include "code\modules\projectiles\boxes_magazines\external\pistol.dm" -#include "code\modules\projectiles\boxes_magazines\external\rechargable.dm" -#include "code\modules\projectiles\boxes_magazines\external\rifle.dm" -#include "code\modules\projectiles\boxes_magazines\external\shotgun.dm" -#include "code\modules\projectiles\boxes_magazines\external\smg.dm" -#include "code\modules\projectiles\boxes_magazines\external\sniper.dm" -#include "code\modules\projectiles\boxes_magazines\external\toy.dm" -#include "code\modules\projectiles\boxes_magazines\internal\_cylinder.dm" -#include "code\modules\projectiles\boxes_magazines\internal\_internal.dm" -#include "code\modules\projectiles\boxes_magazines\internal\grenade.dm" -#include "code\modules\projectiles\boxes_magazines\internal\misc.dm" -#include "code\modules\projectiles\boxes_magazines\internal\revolver.dm" -#include "code\modules\projectiles\boxes_magazines\internal\rifle.dm" -#include "code\modules\projectiles\boxes_magazines\internal\shotgun.dm" -#include "code\modules\projectiles\boxes_magazines\internal\toy.dm" -#include "code\modules\projectiles\guns\ballistic.dm" -#include "code\modules\projectiles\guns\energy.dm" -#include "code\modules\projectiles\guns\magic.dm" -#include "code\modules\projectiles\guns\ballistic\automatic.dm" -#include "code\modules\projectiles\guns\ballistic\laser_gatling.dm" -#include "code\modules\projectiles\guns\ballistic\launchers.dm" -#include "code\modules\projectiles\guns\ballistic\pistol.dm" -#include "code\modules\projectiles\guns\ballistic\revolver.dm" -#include "code\modules\projectiles\guns\ballistic\shotgun.dm" -#include "code\modules\projectiles\guns\ballistic\toy.dm" -#include "code\modules\projectiles\guns\energy\dueling.dm" -#include "code\modules\projectiles\guns\energy\energy_gun.dm" -#include "code\modules\projectiles\guns\energy\kinetic_accelerator.dm" -#include "code\modules\projectiles\guns\energy\laser.dm" -#include "code\modules\projectiles\guns\energy\megabuster.dm" -#include "code\modules\projectiles\guns\energy\mounted.dm" -#include "code\modules\projectiles\guns\energy\plasma_cit.dm" -#include "code\modules\projectiles\guns\energy\pulse.dm" -#include "code\modules\projectiles\guns\energy\special.dm" -#include "code\modules\projectiles\guns\energy\stun.dm" -#include "code\modules\projectiles\guns\magic\staff.dm" -#include "code\modules\projectiles\guns\magic\wand.dm" -#include "code\modules\projectiles\guns\misc\beam_rifle.dm" -#include "code\modules\projectiles\guns\misc\blastcannon.dm" -#include "code\modules\projectiles\guns\misc\chem_gun.dm" -#include "code\modules\projectiles\guns\misc\grenade_launcher.dm" -#include "code\modules\projectiles\guns\misc\medbeam.dm" -#include "code\modules\projectiles\guns\misc\syringe_gun.dm" -#include "code\modules\projectiles\projectile\beams.dm" -#include "code\modules\projectiles\projectile\bullets.dm" -#include "code\modules\projectiles\projectile\magic.dm" -#include "code\modules\projectiles\projectile\megabuster.dm" -#include "code\modules\projectiles\projectile\plasma.dm" -#include "code\modules\projectiles\projectile\bullets\_incendiary.dm" -#include "code\modules\projectiles\projectile\bullets\dart_syringe.dm" -#include "code\modules\projectiles\projectile\bullets\dnainjector.dm" -#include "code\modules\projectiles\projectile\bullets\grenade.dm" -#include "code\modules\projectiles\projectile\bullets\lmg.dm" -#include "code\modules\projectiles\projectile\bullets\pistol.dm" -#include "code\modules\projectiles\projectile\bullets\revolver.dm" -#include "code\modules\projectiles\projectile\bullets\rifle.dm" -#include "code\modules\projectiles\projectile\bullets\shotgun.dm" -#include "code\modules\projectiles\projectile\bullets\smg.dm" -#include "code\modules\projectiles\projectile\bullets\sniper.dm" -#include "code\modules\projectiles\projectile\bullets\special.dm" -#include "code\modules\projectiles\projectile\energy\_energy.dm" -#include "code\modules\projectiles\projectile\energy\ebow.dm" -#include "code\modules\projectiles\projectile\energy\misc.dm" -#include "code\modules\projectiles\projectile\energy\net_snare.dm" -#include "code\modules\projectiles\projectile\energy\nuclear_particle.dm" -#include "code\modules\projectiles\projectile\energy\stun.dm" -#include "code\modules\projectiles\projectile\energy\tesla.dm" -#include "code\modules\projectiles\projectile\magic\spellcard.dm" -#include "code\modules\projectiles\projectile\reusable\_reusable.dm" -#include "code\modules\projectiles\projectile\reusable\foam_dart.dm" -#include "code\modules\projectiles\projectile\reusable\magspear.dm" -#include "code\modules\projectiles\projectile\special\curse.dm" -#include "code\modules\projectiles\projectile\special\floral.dm" -#include "code\modules\projectiles\projectile\special\gravity.dm" -#include "code\modules\projectiles\projectile\special\hallucination.dm" -#include "code\modules\projectiles\projectile\special\ion.dm" -#include "code\modules\projectiles\projectile\special\meteor.dm" -#include "code\modules\projectiles\projectile\special\mindflayer.dm" -#include "code\modules\projectiles\projectile\special\neurotoxin.dm" -#include "code\modules\projectiles\projectile\special\plasma.dm" -#include "code\modules\projectiles\projectile\special\rocket.dm" -#include "code\modules\projectiles\projectile\special\temperature.dm" -#include "code\modules\projectiles\projectile\special\wormhole.dm" -#include "code\modules\reagents\chem_splash.dm" -#include "code\modules\reagents\chem_wiki_render.dm" -#include "code\modules\reagents\reagent_containers.dm" -#include "code\modules\reagents\reagent_dispenser.dm" -#include "code\modules\reagents\chemistry\colors.dm" -#include "code\modules\reagents\chemistry\holder.dm" -#include "code\modules\reagents\chemistry\reagents.dm" -#include "code\modules\reagents\chemistry\recipes.dm" -#include "code\modules\reagents\chemistry\machinery\chem_dispenser.dm" -#include "code\modules\reagents\chemistry\machinery\chem_heater.dm" -#include "code\modules\reagents\chemistry\machinery\chem_master.dm" -#include "code\modules\reagents\chemistry\machinery\chem_synthesizer.dm" -#include "code\modules\reagents\chemistry\machinery\pandemic.dm" -#include "code\modules\reagents\chemistry\machinery\reagentgrinder.dm" -#include "code\modules\reagents\chemistry\machinery\smoke_machine.dm" -#include "code\modules\reagents\chemistry\reagents\alcohol_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\blob_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\drink_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\drug_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\food_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\impure_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\medicine_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\other_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\pyrotechnic_reagents.dm" -#include "code\modules\reagents\chemistry\reagents\toxin_reagents.dm" -#include "code\modules\reagents\chemistry\recipes\drugs.dm" -#include "code\modules\reagents\chemistry\recipes\medicine.dm" -#include "code\modules\reagents\chemistry\recipes\others.dm" -#include "code\modules\reagents\chemistry\recipes\pyrotechnics.dm" -#include "code\modules\reagents\chemistry\recipes\slime_extracts.dm" -#include "code\modules\reagents\chemistry\recipes\special.dm" -#include "code\modules\reagents\chemistry\recipes\toxins.dm" -#include "code\modules\reagents\reagent_containers\blood_pack.dm" -#include "code\modules\reagents\reagent_containers\borghydro.dm" -#include "code\modules\reagents\reagent_containers\bottle.dm" -#include "code\modules\reagents\reagent_containers\chem_pack.dm" -#include "code\modules\reagents\reagent_containers\Chemical_tongue.dm" -#include "code\modules\reagents\reagent_containers\dropper.dm" -#include "code\modules\reagents\reagent_containers\glass.dm" -#include "code\modules\reagents\reagent_containers\hypospray.dm" -#include "code\modules\reagents\reagent_containers\hypovial.dm" -#include "code\modules\reagents\reagent_containers\medspray.dm" -#include "code\modules\reagents\reagent_containers\patch.dm" -#include "code\modules\reagents\reagent_containers\pill.dm" -#include "code\modules\reagents\reagent_containers\rags.dm" -#include "code\modules\reagents\reagent_containers\sleeper_buffer.dm" -#include "code\modules\reagents\reagent_containers\spray.dm" -#include "code\modules\reagents\reagent_containers\syringes.dm" -#include "code\modules\recycling\conveyor2.dm" -#include "code\modules\recycling\sortingmachinery.dm" -#include "code\modules\recycling\disposal\bin.dm" -#include "code\modules\recycling\disposal\construction.dm" -#include "code\modules\recycling\disposal\eject.dm" -#include "code\modules\recycling\disposal\holder.dm" -#include "code\modules\recycling\disposal\multiz.dm" -#include "code\modules\recycling\disposal\outlet.dm" -#include "code\modules\recycling\disposal\pipe.dm" -#include "code\modules\recycling\disposal\pipe_sorting.dm" -#include "code\modules\research\designs.dm" -#include "code\modules\research\destructive_analyzer.dm" -#include "code\modules\research\experimentor.dm" -#include "code\modules\research\rdconsole.dm" -#include "code\modules\research\rdmachines.dm" -#include "code\modules\research\research_disk.dm" -#include "code\modules\research\server.dm" -#include "code\modules\research\stock_parts.dm" -#include "code\modules\research\designs\AI_module_designs.dm" -#include "code\modules\research\designs\autobotter_designs.dm" -#include "code\modules\research\designs\biogenerator_designs.dm" -#include "code\modules\research\designs\bluespace_designs.dm" -#include "code\modules\research\designs\computer_part_designs.dm" -#include "code\modules\research\designs\electronics_designs.dm" -#include "code\modules\research\designs\equipment_designs.dm" -#include "code\modules\research\designs\limbgrower_designs.dm" -#include "code\modules\research\designs\mecha_designs.dm" -#include "code\modules\research\designs\mechfabricator_designs.dm" -#include "code\modules\research\designs\medical_designs.dm" -#include "code\modules\research\designs\mining_designs.dm" -#include "code\modules\research\designs\misc_designs.dm" -#include "code\modules\research\designs\nanite_designs.dm" -#include "code\modules\research\designs\power_designs.dm" -#include "code\modules\research\designs\smelting_designs.dm" -#include "code\modules\research\designs\stock_parts_designs.dm" -#include "code\modules\research\designs\telecomms_designs.dm" -#include "code\modules\research\designs\weapon_designs.dm" -#include "code\modules\research\designs\autolathe_desings\autolathe_designs_construction.dm" -#include "code\modules\research\designs\autolathe_desings\autolathe_designs_electronics.dm" -#include "code\modules\research\designs\autolathe_desings\autolathe_designs_medical_and_dinnerware.dm" -#include "code\modules\research\designs\autolathe_desings\autolathe_designs_sec_and_hacked.dm" -#include "code\modules\research\designs\autolathe_desings\autolathe_designs_tcomms_and_misc.dm" -#include "code\modules\research\designs\autolathe_desings\autolathe_designs_tools.dm" -#include "code\modules\research\designs\comp_board_designs\comp_board_designs_all_misc.dm" -#include "code\modules\research\designs\comp_board_designs\comp_board_designs_cargo .dm" -#include "code\modules\research\designs\comp_board_designs\comp_board_designs_engi.dm" -#include "code\modules\research\designs\comp_board_designs\comp_board_designs_medical.dm" -#include "code\modules\research\designs\comp_board_designs\comp_board_designs_sci.dm" -#include "code\modules\research\designs\comp_board_designs\comp_board_designs_sec.dm" -#include "code\modules\research\designs\machine_desings\machine_designs_all_misc.dm" -#include "code\modules\research\designs\machine_desings\machine_designs_cargo.dm" -#include "code\modules\research\designs\machine_desings\machine_designs_engi.dm" -#include "code\modules\research\designs\machine_desings\machine_designs_medical.dm" -#include "code\modules\research\designs\machine_desings\machine_designs_sci.dm" -#include "code\modules\research\designs\machine_desings\machine_designs_service.dm" -#include "code\modules\research\machinery\_production.dm" -#include "code\modules\research\machinery\circuit_imprinter.dm" -#include "code\modules\research\machinery\departmental_circuit_imprinter.dm" -#include "code\modules\research\machinery\departmental_protolathe.dm" -#include "code\modules\research\machinery\departmental_techfab.dm" -#include "code\modules\research\machinery\protolathe.dm" -#include "code\modules\research\machinery\techfab.dm" -#include "code\modules\research\nanites\nanite_chamber.dm" -#include "code\modules\research\nanites\nanite_chamber_computer.dm" -#include "code\modules\research\nanites\nanite_cloud_controller.dm" -#include "code\modules\research\nanites\nanite_hijacker.dm" -#include "code\modules\research\nanites\nanite_misc_items.dm" -#include "code\modules\research\nanites\nanite_program_hub.dm" -#include "code\modules\research\nanites\nanite_programmer.dm" -#include "code\modules\research\nanites\nanite_programs.dm" -#include "code\modules\research\nanites\nanite_remote.dm" -#include "code\modules\research\nanites\program_disks.dm" -#include "code\modules\research\nanites\public_chamber.dm" -#include "code\modules\research\nanites\nanite_programs\buffing.dm" -#include "code\modules\research\nanites\nanite_programs\healing.dm" -#include "code\modules\research\nanites\nanite_programs\rogue.dm" -#include "code\modules\research\nanites\nanite_programs\sensor.dm" -#include "code\modules\research\nanites\nanite_programs\suppression.dm" -#include "code\modules\research\nanites\nanite_programs\utility.dm" -#include "code\modules\research\nanites\nanite_programs\weapon.dm" -#include "code\modules\research\techweb\__techweb_helpers.dm" -#include "code\modules\research\techweb\_techweb.dm" -#include "code\modules\research\techweb\_techweb_node.dm" -#include "code\modules\research\techweb\all_nodes.dm" -#include "code\modules\research\xenoarch\artifact.dm" -#include "code\modules\research\xenoarch\artifact_list.dm" -#include "code\modules\research\xenoarch\strange_rock.dm" -#include "code\modules\research\xenoarch\tools.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\amauri.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\gelthi.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\jurlmah.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\nofruit.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\shand.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\surik.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\telriis.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\thaadra.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\vale.dm" -#include "code\modules\research\xenoarch\xenobotany\grown\vaporsac.dm" -#include "code\modules\research\xenobiology\xenobio_camera.dm" -#include "code\modules\research\xenobiology\xenobiology.dm" -#include "code\modules\research\xenobiology\crossbreeding\__corecross.dm" -#include "code\modules\research\xenobiology\crossbreeding\_clothing.dm" -#include "code\modules\research\xenobiology\crossbreeding\_misc.dm" -#include "code\modules\research\xenobiology\crossbreeding\_mobs.dm" -#include "code\modules\research\xenobiology\crossbreeding\_status_effects.dm" -#include "code\modules\research\xenobiology\crossbreeding\_weapons.dm" -#include "code\modules\research\xenobiology\crossbreeding\burning.dm" -#include "code\modules\research\xenobiology\crossbreeding\charged.dm" -#include "code\modules\research\xenobiology\crossbreeding\chilling.dm" -#include "code\modules\research\xenobiology\crossbreeding\consuming.dm" -#include "code\modules\research\xenobiology\crossbreeding\industrial.dm" -#include "code\modules\research\xenobiology\crossbreeding\prismatic.dm" -#include "code\modules\research\xenobiology\crossbreeding\recurring.dm" -#include "code\modules\research\xenobiology\crossbreeding\regenerative.dm" -#include "code\modules\research\xenobiology\crossbreeding\reproductive.dm" -#include "code\modules\research\xenobiology\crossbreeding\selfsustaining.dm" -#include "code\modules\research\xenobiology\crossbreeding\stabilized.dm" -#include "code\modules\ruins\lavaland_ruin_code.dm" -#include "code\modules\ruins\lavalandruin_code\biodome_clown_planet.dm" -#include "code\modules\ruins\lavalandruin_code\pizzaparty.dm" -#include "code\modules\ruins\lavalandruin_code\puzzle.dm" -#include "code\modules\ruins\lavalandruin_code\sloth.dm" -#include "code\modules\ruins\lavalandruin_code\surface.dm" -#include "code\modules\ruins\lavalandruin_code\syndicate_base.dm" -#include "code\modules\ruins\objects_and_mobs\ash_walker_den.dm" -#include "code\modules\ruins\objects_and_mobs\necropolis_gate.dm" -#include "code\modules\ruins\objects_and_mobs\sin_ruins.dm" -#include "code\modules\ruins\spaceruin_code\asteroid4.dm" -#include "code\modules\ruins\spaceruin_code\bigderelict1.dm" -#include "code\modules\ruins\spaceruin_code\caravanambush.dm" -#include "code\modules\ruins\spaceruin_code\cloning_lab.dm" -#include "code\modules\ruins\spaceruin_code\crashedclownship.dm" -#include "code\modules\ruins\spaceruin_code\crashedship.dm" -#include "code\modules\ruins\spaceruin_code\deepstorage.dm" -#include "code\modules\ruins\spaceruin_code\DJstation.dm" -#include "code\modules\ruins\spaceruin_code\hilbertshotel.dm" -#include "code\modules\ruins\spaceruin_code\listeningstation.dm" -#include "code\modules\ruins\spaceruin_code\miracle.dm" -#include "code\modules\ruins\spaceruin_code\oldstation.dm" -#include "code\modules\ruins\spaceruin_code\originalcontent.dm" -#include "code\modules\ruins\spaceruin_code\spacehotel.dm" -#include "code\modules\ruins\spaceruin_code\TheDerelict.dm" -#include "code\modules\ruins\spaceruin_code\whiteshipruin_box.dm" -#include "code\modules\security_levels\keycard_authentication.dm" -#include "code\modules\security_levels\security_levels.dm" -#include "code\modules\shuttle\arrivals.dm" -#include "code\modules\shuttle\assault_pod.dm" -#include "code\modules\shuttle\computer.dm" -#include "code\modules\shuttle\docking.dm" -#include "code\modules\shuttle\elevator.dm" -#include "code\modules\shuttle\emergency.dm" -#include "code\modules\shuttle\ferry.dm" -#include "code\modules\shuttle\manipulator.dm" -#include "code\modules\shuttle\monastery.dm" -#include "code\modules\shuttle\navigation_computer.dm" -#include "code\modules\shuttle\on_move.dm" -#include "code\modules\shuttle\ripple.dm" -#include "code\modules\shuttle\shuttle.dm" -#include "code\modules\shuttle\shuttle_rotate.dm" -#include "code\modules\shuttle\special.dm" -#include "code\modules\shuttle\supply.dm" -#include "code\modules\shuttle\syndicate.dm" -#include "code\modules\shuttle\white_ship.dm" -#include "code\modules\spells\spell.dm" -#include "code\modules\spells\spell_types\adminbussed.dm" -#include "code\modules\spells\spell_types\aimed.dm" -#include "code\modules\spells\spell_types\area_teleport.dm" -#include "code\modules\spells\spell_types\barnyard.dm" -#include "code\modules\spells\spell_types\bloodcrawl.dm" -#include "code\modules\spells\spell_types\charge.dm" -#include "code\modules\spells\spell_types\conjure.dm" -#include "code\modules\spells\spell_types\construct_spells.dm" -#include "code\modules\spells\spell_types\devil.dm" -#include "code\modules\spells\spell_types\devil_boons.dm" -#include "code\modules\spells\spell_types\dumbfire.dm" -#include "code\modules\spells\spell_types\emplosion.dm" -#include "code\modules\spells\spell_types\ethereal_jaunt.dm" -#include "code\modules\spells\spell_types\explosion.dm" -#include "code\modules\spells\spell_types\forcewall.dm" -#include "code\modules\spells\spell_types\genetic.dm" -#include "code\modules\spells\spell_types\godhand.dm" -#include "code\modules\spells\spell_types\infinite_guns.dm" -#include "code\modules\spells\spell_types\inflict_handler.dm" -#include "code\modules\spells\spell_types\knock.dm" -#include "code\modules\spells\spell_types\lichdom.dm" -#include "code\modules\spells\spell_types\lightning.dm" -#include "code\modules\spells\spell_types\mime.dm" -#include "code\modules\spells\spell_types\mind_transfer.dm" -#include "code\modules\spells\spell_types\projectile.dm" -#include "code\modules\spells\spell_types\rightandwrong.dm" -#include "code\modules\spells\spell_types\rod_form.dm" -#include "code\modules\spells\spell_types\santa.dm" -#include "code\modules\spells\spell_types\shadow_walk.dm" -#include "code\modules\spells\spell_types\shapeshift.dm" -#include "code\modules\spells\spell_types\spacetime_distortion.dm" -#include "code\modules\spells\spell_types\summonitem.dm" -#include "code\modules\spells\spell_types\taeclowndo.dm" -#include "code\modules\spells\spell_types\telepathy.dm" -#include "code\modules\spells\spell_types\the_traps.dm" -#include "code\modules\spells\spell_types\touch_attacks.dm" -#include "code\modules\spells\spell_types\trigger.dm" -#include "code\modules\spells\spell_types\turf_teleport.dm" -#include "code\modules\spells\spell_types\voice_of_god.dm" -#include "code\modules\spells\spell_types\wizard.dm" -#include "code\modules\station_goals\bsa.dm" -#include "code\modules\station_goals\dna_vault.dm" -#include "code\modules\station_goals\shield.dm" -#include "code\modules\station_goals\station_goal.dm" -#include "code\modules\surgery\amputation.dm" -#include "code\modules\surgery\brain_surgery.dm" -#include "code\modules\surgery\breast_augmentation.dm" -#include "code\modules\surgery\cavity_implant.dm" -#include "code\modules\surgery\core_removal.dm" -#include "code\modules\surgery\coronary_bypass.dm" -#include "code\modules\surgery\dental_implant.dm" -#include "code\modules\surgery\embalming.dm" -#include "code\modules\surgery\experimental_dissection.dm" -#include "code\modules\surgery\eye_surgery.dm" -#include "code\modules\surgery\graft_synthtissue.dm" -#include "code\modules\surgery\healing.dm" -#include "code\modules\surgery\helpers.dm" -#include "code\modules\surgery\implant_removal.dm" -#include "code\modules\surgery\limb_augmentation.dm" -#include "code\modules\surgery\lipoplasty.dm" -#include "code\modules\surgery\lobectomy.dm" -#include "code\modules\surgery\mechanic_steps.dm" -#include "code\modules\surgery\nutcracker.dm" -#include "code\modules\surgery\organ_manipulation.dm" -#include "code\modules\surgery\organic_steps.dm" -#include "code\modules\surgery\penis_augmentation.dm" -#include "code\modules\surgery\plastic_surgery.dm" -#include "code\modules\surgery\prosthetic_replacement.dm" -#include "code\modules\surgery\remove_embedded_object.dm" -#include "code\modules\surgery\surgery.dm" -#include "code\modules\surgery\surgery_step.dm" -#include "code\modules\surgery\tools.dm" -#include "code\modules\surgery\advanced\brainwashing.dm" -#include "code\modules\surgery\advanced\lobotomy.dm" -#include "code\modules\surgery\advanced\necrotic_revival.dm" -#include "code\modules\surgery\advanced\pacification.dm" -#include "code\modules\surgery\advanced\revival.dm" -#include "code\modules\surgery\advanced\toxichealing.dm" -#include "code\modules\surgery\advanced\viral_bonding.dm" -#include "code\modules\surgery\advanced\bioware\bioware.dm" -#include "code\modules\surgery\advanced\bioware\bioware_surgery.dm" -#include "code\modules\surgery\advanced\bioware\ligament_hook.dm" -#include "code\modules\surgery\advanced\bioware\ligament_reinforcement.dm" -#include "code\modules\surgery\advanced\bioware\muscled_veins.dm" -#include "code\modules\surgery\advanced\bioware\nerve_grounding.dm" -#include "code\modules\surgery\advanced\bioware\nerve_splicing.dm" -#include "code\modules\surgery\advanced\bioware\vein_threading.dm" -#include "code\modules\surgery\bodyparts\bodyparts.dm" -#include "code\modules\surgery\bodyparts\broken.dm" -#include "code\modules\surgery\bodyparts\dismemberment.dm" -#include "code\modules\surgery\bodyparts\head.dm" -#include "code\modules\surgery\bodyparts\helpers.dm" -#include "code\modules\surgery\bodyparts\robot_bodyparts.dm" -#include "code\modules\surgery\organs\appendix.dm" -#include "code\modules\surgery\organs\augments_arms.dm" -#include "code\modules\surgery\organs\augments_chest.dm" -#include "code\modules\surgery\organs\augments_eyes.dm" -#include "code\modules\surgery\organs\augments_internal.dm" -#include "code\modules\surgery\organs\autosurgeon.dm" -#include "code\modules\surgery\organs\ears.dm" -#include "code\modules\surgery\organs\eyes.dm" -#include "code\modules\surgery\organs\heart.dm" -#include "code\modules\surgery\organs\helpers.dm" -#include "code\modules\surgery\organs\liver.dm" -#include "code\modules\surgery\organs\lungs.dm" -#include "code\modules\surgery\organs\organ_internal.dm" -#include "code\modules\surgery\organs\stomach.dm" -#include "code\modules\surgery\organs\tails.dm" -#include "code\modules\surgery\organs\tongue.dm" -#include "code\modules\surgery\organs\vocal_cords.dm" -#include "code\modules\tgs\includes.dm" -#include "code\modules\tgui\external.dm" -#include "code\modules\tgui\states.dm" -#include "code\modules\tgui\subsystem.dm" -#include "code\modules\tgui\tgui.dm" -#include "code\modules\tgui\states\admin.dm" -#include "code\modules\tgui\states\always.dm" -#include "code\modules\tgui\states\conscious.dm" -#include "code\modules\tgui\states\contained.dm" -#include "code\modules\tgui\states\deep_inventory.dm" -#include "code\modules\tgui\states\default.dm" -#include "code\modules\tgui\states\hands.dm" -#include "code\modules\tgui\states\human_adjacent.dm" -#include "code\modules\tgui\states\inventory.dm" -#include "code\modules\tgui\states\language_menu.dm" -#include "code\modules\tgui\states\not_incapacitated.dm" -#include "code\modules\tgui\states\notcontained.dm" -#include "code\modules\tgui\states\observer.dm" -#include "code\modules\tgui\states\physical.dm" -#include "code\modules\tgui\states\self.dm" -#include "code\modules\tgui\states\zlevel.dm" -#include "code\modules\tooltip\tooltip.dm" -#include "code\modules\unit_tests\_unit_tests.dm" -#include "code\modules\uplink\uplink_devices.dm" -#include "code\modules\uplink\uplink_items.dm" -#include "code\modules\uplink\uplink_purchase_log.dm" -#include "code\modules\vehicles\_vehicle.dm" -#include "code\modules\vehicles\atv.dm" -#include "code\modules\vehicles\bicycle.dm" -#include "code\modules\vehicles\lavaboat.dm" -#include "code\modules\vehicles\motorized_wheelchair.dm" -#include "code\modules\vehicles\pimpin_ride.dm" -#include "code\modules\vehicles\ridden.dm" -#include "code\modules\vehicles\scooter.dm" -#include "code\modules\vehicles\sealed.dm" -#include "code\modules\vehicles\secway.dm" -#include "code\modules\vehicles\speedbike.dm" -#include "code\modules\vehicles\vehicle_actions.dm" -#include "code\modules\vehicles\vehicle_key.dm" -#include "code\modules\vehicles\wheelchair.dm" -#include "code\modules\vehicles\cars\car.dm" -#include "code\modules\vehicles\cars\clowncar.dm" -#include "code\modules\vending\_vending.dm" -#include "code\modules\vending\assist.dm" -#include "code\modules\vending\autodrobe.dm" -#include "code\modules\vending\boozeomat.dm" -#include "code\modules\vending\cartridge.dm" -#include "code\modules\vending\cigarette.dm" -#include "code\modules\vending\clothesmate.dm" -#include "code\modules\vending\coffee.dm" -#include "code\modules\vending\cola.dm" -#include "code\modules\vending\drinnerware.dm" -#include "code\modules\vending\engineering.dm" -#include "code\modules\vending\engivend.dm" -#include "code\modules\vending\games.dm" -#include "code\modules\vending\liberation.dm" -#include "code\modules\vending\liberation_toy.dm" -#include "code\modules\vending\magivend.dm" -#include "code\modules\vending\medical.dm" -#include "code\modules\vending\medical_wall.dm" -#include "code\modules\vending\megaseed.dm" -#include "code\modules\vending\nutrimax.dm" -#include "code\modules\vending\plasmaresearch.dm" -#include "code\modules\vending\robotics.dm" -#include "code\modules\vending\security.dm" -#include "code\modules\vending\snack.dm" -#include "code\modules\vending\sovietsoda.dm" -#include "code\modules\vending\sustenance.dm" -#include "code\modules\vending\toys.dm" -#include "code\modules\vending\wardrobes.dm" -#include "code\modules\vending\youtool.dm" -#include "code\modules\VR\vr_human.dm" -#include "code\modules\VR\vr_sleeper.dm" -#include "code\modules\zombie\items.dm" -#include "code\modules\zombie\organs.dm" -#include "hyperstation\code\datums\elements\holder_micro.dm" -#include "hyperstation\code\datums\mood_events\events.dm" -#include "hyperstation\code\datums\ruins\lavaland.dm" -#include "hyperstation\code\datums\traits\good.dm" -#include "hyperstation\code\datums\traits\neutral.dm" -#include "hyperstation\code\game\objects\structures\ghost_role_spawners.dm" -#include "hyperstation\code\gamemode\traitor_lewd.dm" -#include "hyperstation\code\gamemode\traitor_thief.dm" -#include "hyperstation\code\gamemode\werewolf\werewolf.dm" -#include "hyperstation\code\mobs\carrion.dm" -#include "hyperstation\code\mobs\hugbot.dm" -#include "hyperstation\code\mobs\mimic.dm" -#include "hyperstation\code\mobs\werewolf.dm" -#include "hyperstation\code\modules\traits.dm" -#include "hyperstation\code\modules\antagonists\werewolf\werewolf.dm" -#include "hyperstation\code\modules\arousal\arousalhud.dm" -#include "hyperstation\code\modules\client\loadout\glasses.dm" -#include "hyperstation\code\modules\client\loadout\tablet.dm" -#include "hyperstation\code\modules\clothing\head.dm" -#include "hyperstation\code\modules\clothing\glasses\polychromic_glasses.dm" -#include "hyperstation\code\modules\clothing\spacesuits\hardsuit.dm" -#include "hyperstation\code\modules\clothing\suits\misc.dm" -#include "hyperstation\code\modules\crafting\bounties.dm" -#include "hyperstation\code\modules\crafting\recipes.dm" -#include "hyperstation\code\modules\integrated_electronics\input.dm" -#include "hyperstation\code\modules\mob\mob_helpers.dm" -#include "hyperstation\code\modules\patreon\patreon.dm" -#include "hyperstation\code\modules\resize\resizing.dm" -#include "hyperstation\code\modules\resize\sizechems.dm" -#include "hyperstation\code\modules\resize\sizegun.dm" -#include "hyperstation\code\obj\bluespace sewing kit.dm" -#include "hyperstation\code\obj\condom.dm" -#include "hyperstation\code\obj\decal.dm" -#include "hyperstation\code\obj\fluff.dm" -#include "hyperstation\code\obj\kinkyclothes.dm" -#include "hyperstation\code\obj\leash.dm" -#include "hyperstation\code\obj\lunaritems.dm" -#include "hyperstation\code\obj\milking machine.dm" -#include "hyperstation\code\obj\plushes.dm" -#include "hyperstation\code\obj\pregnancytester.dm" -#include "hyperstation\code\obj\rewards.dm" -#include "hyperstation\code\obj\rope.dm" -#include "hyperstation\code\obj\sounding.dm" -#include "interface\interface.dm" -#include "interface\menu.dm" -#include "interface\stylesheet.dm" -#include "interface\skin.dmf" -#include "modular_citadel\code\init.dm" -#include "modular_citadel\code\__HELPERS\list2list.dm" -#include "modular_citadel\code\__HELPERS\lists.dm" -#include "modular_citadel\code\__HELPERS\mobs.dm" -#include "modular_citadel\code\_globalvars\lists\mobs.dm" -#include "modular_citadel\code\_onclick\click.dm" -#include "modular_citadel\code\_onclick\item_attack.dm" -#include "modular_citadel\code\_onclick\other_mobs.dm" -#include "modular_citadel\code\_onclick\hud\screen_objects.dm" -#include "modular_citadel\code\_onclick\hud\sprint.dm" -#include "modular_citadel\code\_onclick\hud\stamina.dm" -#include "modular_citadel\code\controllers\configuration\entries\general.dm" -#include "modular_citadel\code\controllers\subsystem\job.dm" -#include "modular_citadel\code\datums\components\material_container.dm" -#include "modular_citadel\code\datums\components\phantomthief.dm" -#include "modular_citadel\code\datums\components\souldeath.dm" -#include "modular_citadel\code\datums\mood_events\chem_events.dm" -#include "modular_citadel\code\datums\mood_events\generic_negative_events.dm" -#include "modular_citadel\code\datums\mood_events\generic_positive_events.dm" -#include "modular_citadel\code\datums\mood_events\moodular.dm" -#include "modular_citadel\code\datums\mutations\hulk.dm" -#include "modular_citadel\code\datums\status_effects\chems.dm" -#include "modular_citadel\code\datums\status_effects\debuffs.dm" -#include "modular_citadel\code\datums\traits\negative.dm" -#include "modular_citadel\code\datums\traits\neutral.dm" -#include "modular_citadel\code\datums\wires\airlock.dm" -#include "modular_citadel\code\datums\wires\autoylathe.dm" -#include "modular_citadel\code\game\area\cit_areas.dm" -#include "modular_citadel\code\game\gamemodes\gangs\dominator.dm" -#include "modular_citadel\code\game\gamemodes\gangs\dominator_countdown.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gang.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gang_datums.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gang_decals.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gang_hud.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gang_items.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gang_pen.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gangs.dm" -#include "modular_citadel\code\game\gamemodes\gangs\gangtool.dm" -#include "modular_citadel\code\game\gamemodes\gangs\implant_gang.dm" -#include "modular_citadel\code\game\gamemodes\miniantags\bot_swarm\swarmer_event.dm" -#include "modular_citadel\code\game\gamemodes\revolution\revolution.dm" -#include "modular_citadel\code\game\machinery\cryopod.dm" -#include "modular_citadel\code\game\machinery\displaycases.dm" -#include "modular_citadel\code\game\machinery\Sleeper.dm" -#include "modular_citadel\code\game\machinery\toylathe.dm" -#include "modular_citadel\code\game\machinery\vending.dm" -#include "modular_citadel\code\game\machinery\wishgranter.dm" -#include "modular_citadel\code\game\machinery\doors\airlock.dm" -#include "modular_citadel\code\game\machinery\doors\airlock_types.dm" -#include "modular_citadel\code\game\objects\cit_screenshake.dm" -#include "modular_citadel\code\game\objects\items.dm" -#include "modular_citadel\code\game\objects\tools.dm" -#include "modular_citadel\code\game\objects\effects\spawner\spawners.dm" -#include "modular_citadel\code\game\objects\effects\temporary_visuals\souldeath.dm" -#include "modular_citadel\code\game\objects\effects\temporary_visuals\projectiles\impact.dm" -#include "modular_citadel\code\game\objects\effects\temporary_visuals\projectiles\muzzle.dm" -#include "modular_citadel\code\game\objects\effects\temporary_visuals\projectiles\tracer.dm" -#include "modular_citadel\code\game\objects\items\balls.dm" -#include "modular_citadel\code\game\objects\items\boombox.dm" -#include "modular_citadel\code\game\objects\items\holy_weapons.dm" -#include "modular_citadel\code\game\objects\items\honk.dm" -#include "modular_citadel\code\game\objects\items\stunsword.dm" -#include "modular_citadel\code\game\objects\items\vending_items.dm" -#include "modular_citadel\code\game\objects\items\circuitboards\machine_circuitboards.dm" -#include "modular_citadel\code\game\objects\items\devices\aicard.dm" -#include "modular_citadel\code\game\objects\items\devices\radio\encryptionkey.dm" -#include "modular_citadel\code\game\objects\items\devices\radio\headset.dm" -#include "modular_citadel\code\game\objects\items\devices\radio\shockcollar.dm" -#include "modular_citadel\code\game\objects\items\melee\eutactic_blades.dm" -#include "modular_citadel\code\game\objects\items\melee\misc.dm" -#include "modular_citadel\code\game\objects\items\robot\robot_upgrades.dm" -#include "modular_citadel\code\game\objects\items\storage\firstaid.dm" -#include "modular_citadel\code\game\objects\structures\tables_racks.dm" -#include "modular_citadel\code\game\objects\structures\beds_chairs\chair.dm" -#include "modular_citadel\code\game\objects\structures\beds_chairs\sofa.dm" -#include "modular_citadel\code\game\objects\structures\crates_lockers\closets\fitness.dm" -#include "modular_citadel\code\game\objects\structures\crates_lockers\closets\wardrobe.dm" -#include "modular_citadel\code\game\objects\structures\crates_lockers\closets\secure\citadel_lockers.dm" -#include "modular_citadel\code\game\turfs\cit_turfs.dm" -#include "modular_citadel\code\modules\admin\chat_commands.dm" -#include "modular_citadel\code\modules\admin\holder2.dm" -#include "modular_citadel\code\modules\admin\secrets.dm" -#include "modular_citadel\code\modules\arousal\arousal.dm" -#include "modular_citadel\code\modules\arousal\organs\breasts.dm" -#include "modular_citadel\code\modules\arousal\organs\eggsack.dm" -#include "modular_citadel\code\modules\arousal\organs\genitals.dm" -#include "modular_citadel\code\modules\arousal\organs\genitals_sprite_accessories.dm" -#include "modular_citadel\code\modules\arousal\organs\ovipositor.dm" -#include "modular_citadel\code\modules\arousal\organs\penis.dm" -#include "modular_citadel\code\modules\arousal\organs\testicles.dm" -#include "modular_citadel\code\modules\arousal\organs\vagina.dm" -#include "modular_citadel\code\modules\arousal\organs\womb.dm" -#include "modular_citadel\code\modules\arousal\toys\dildos.dm" -#include "modular_citadel\code\modules\awaymissions\citadel_ghostrole_spawners.dm" -#include "modular_citadel\code\modules\cargo\console.dm" -#include "modular_citadel\code\modules\client\client_defines.dm" -#include "modular_citadel\code\modules\client\client_procs.dm" -#include "modular_citadel\code\modules\client\preferences.dm" -#include "modular_citadel\code\modules\client\preferences_savefile.dm" -#include "modular_citadel\code\modules\client\preferences_toggles.dm" -#include "modular_citadel\code\modules\client\loadout\__donator.dm" -#include "modular_citadel\code\modules\client\loadout\_medical.dm" -#include "modular_citadel\code\modules\client\loadout\_security.dm" -#include "modular_citadel\code\modules\client\loadout\_service.dm" -#include "modular_citadel\code\modules\client\loadout\backpack.dm" -#include "modular_citadel\code\modules\client\loadout\glasses.dm" -#include "modular_citadel\code\modules\client\loadout\gloves.dm" -#include "modular_citadel\code\modules\client\loadout\hands.dm" -#include "modular_citadel\code\modules\client\loadout\head.dm" -#include "modular_citadel\code\modules\client\loadout\loadout.dm" -#include "modular_citadel\code\modules\client\loadout\mask.dm" -#include "modular_citadel\code\modules\client\loadout\neck.dm" -#include "modular_citadel\code\modules\client\loadout\shoes.dm" -#include "modular_citadel\code\modules\client\loadout\suit.dm" -#include "modular_citadel\code\modules\client\loadout\uniform.dm" -#include "modular_citadel\code\modules\client\verbs\who.dm" -#include "modular_citadel\code\modules\clothing\clothing.dm" -#include "modular_citadel\code\modules\clothing\neck.dm" -#include "modular_citadel\code\modules\clothing\glasses\phantomthief.dm" -#include "modular_citadel\code\modules\clothing\head\head.dm" -#include "modular_citadel\code\modules\clothing\spacesuits\flightsuit.dm" -#include "modular_citadel\code\modules\clothing\suits\polychromic_cloaks.dm" -#include "modular_citadel\code\modules\clothing\suits\polychromic_suit.dm" -#include "modular_citadel\code\modules\clothing\suits\suits.dm" -#include "modular_citadel\code\modules\clothing\under\polychromic_clothes.dm" -#include "modular_citadel\code\modules\clothing\under\trek_under.dm" -#include "modular_citadel\code\modules\clothing\under\turtlenecks.dm" -#include "modular_citadel\code\modules\clothing\under\under.dm" -#include "modular_citadel\code\modules\crafting\recipes.dm" -#include "modular_citadel\code\modules\custom_loadout\custom_items.dm" -#include "modular_citadel\code\modules\custom_loadout\load_to_mob.dm" -#include "modular_citadel\code\modules\custom_loadout\read_from_file.dm" -#include "modular_citadel\code\modules\events\blob.dm" -#include "modular_citadel\code\modules\events\wizard\magicarp.dm" -#include "modular_citadel\code\modules\integrated_electronics\subtypes\manipulation.dm" -#include "modular_citadel\code\modules\jobs\dresscode_values.dm" -#include "modular_citadel\code\modules\mentor\follow.dm" -#include "modular_citadel\code\modules\mentor\mentor.dm" -#include "modular_citadel\code\modules\mentor\mentor_memo.dm" -#include "modular_citadel\code\modules\mentor\mentor_verbs.dm" -#include "modular_citadel\code\modules\mentor\mentorhelp.dm" -#include "modular_citadel\code\modules\mentor\mentorpm.dm" -#include "modular_citadel\code\modules\mentor\mentorsay.dm" -#include "modular_citadel\code\modules\mining\mining_ruins.dm" -#include "modular_citadel\code\modules\mob\cit_emotes.dm" -#include "modular_citadel\code\modules\mob\dead\new_player\sprite_accessories.dm" -#include "modular_citadel\code\modules\mob\living\damage_procs.dm" -#include "modular_citadel\code\modules\mob\living\living.dm" -#include "modular_citadel\code\modules\mob\living\carbon\carbon.dm" -#include "modular_citadel\code\modules\mob\living\carbon\damage_procs.dm" -#include "modular_citadel\code\modules\mob\living\carbon\life.dm" -#include "modular_citadel\code\modules\mob\living\carbon\reindex_screams.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\human.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\human_defense.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\human_movement.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\life.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\species.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\furrypeople.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\ipc.dm" -#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\jellypeople.dm" -#include "modular_citadel\code\modules\mob\living\silicon\robot\dogborg_equipment.dm" -#include "modular_citadel\code\modules\mob\living\silicon\robot\robot.dm" -#include "modular_citadel\code\modules\mob\living\silicon\robot\robot_modules.dm" -#include "modular_citadel\code\modules\mob\living\silicon\robot\robot_movement.dm" -#include "modular_citadel\code\modules\mob\living\simple_animal\banana_spider.dm" -#include "modular_citadel\code\modules\mob\living\simple_animal\kiwi.dm" -#include "modular_citadel\code\modules\mob\living\simple_animal\simplemob_vore_values.dm" -#include "modular_citadel\code\modules\power\lighting.dm" -#include "modular_citadel\code\modules\projectiles\gun.dm" -#include "modular_citadel\code\modules\projectiles\ammunition\caseless.dm" -#include "modular_citadel\code\modules\projectiles\ammunition\ballistic\smg\smg.dm" -#include "modular_citadel\code\modules\projectiles\boxes_magazines\ammo_boxes.dm" -#include "modular_citadel\code\modules\projectiles\boxes_magazines\external\pistol.dm" -#include "modular_citadel\code\modules\projectiles\boxes_magazines\external\smg\smg.dm" -#include "modular_citadel\code\modules\projectiles\bullets\bullets\smg.dm" -#include "modular_citadel\code\modules\projectiles\guns\pumpenergy.dm" -#include "modular_citadel\code\modules\projectiles\guns\toys.dm" -#include "modular_citadel\code\modules\projectiles\guns\ballistic\handguns.dm" -#include "modular_citadel\code\modules\projectiles\guns\ballistic\magweapon.dm" -#include "modular_citadel\code\modules\projectiles\guns\ballistic\magweapon_energy.dm" -#include "modular_citadel\code\modules\projectiles\guns\ballistic\revolver.dm" -#include "modular_citadel\code\modules\projectiles\guns\ballistic\rifles.dm" -#include "modular_citadel\code\modules\projectiles\guns\ballistic\spinfusor.dm" -#include "modular_citadel\code\modules\projectiles\guns\energy\energy_gun.dm" -#include "modular_citadel\code\modules\projectiles\guns\energy\laser.dm" -#include "modular_citadel\code\modules\projectiles\projectiles\reusable.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\astrogen.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\eigentstasium.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\enlargement.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\fermi_reagents.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\healing.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\MKUltra.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\other_reagents.dm" -#include "modular_citadel\code\modules\reagents\chemistry\reagents\SDGF.dm" -#include "modular_citadel\code\modules\reagents\chemistry\recipes\fermi.dm" -#include "modular_citadel\code\modules\reagents\objects\clothes.dm" -#include "modular_citadel\code\modules\reagents\objects\items.dm" -#include "modular_citadel\code\modules\reagents\reagents\cit_reagents.dm" -#include "modular_citadel\code\modules\recycling\disposal\bin.dm" -#include "modular_citadel\code\modules\research\designs\autoylathe_designs.dm" -#include "modular_citadel\code\modules\research\designs\machine_designs.dm" -#include "modular_citadel\code\modules\research\designs\xenobio_designs.dm" -#include "modular_citadel\code\modules\research\techweb\_techweb.dm" -#include "modular_citadel\code\modules\research\xenobiology\xenobio_camera.dm" -#include "modular_citadel\code\modules\vehicles\secway.dm" -#include "modular_citadel\code\modules\vore\hook-defs_vr.dm" -#include "modular_citadel\code\modules\vore\persistence.dm" -#include "modular_citadel\code\modules\vore\trycatch_vr.dm" -#include "modular_citadel\code\modules\vore\eating\belly_dat_vr.dm" -#include "modular_citadel\code\modules\vore\eating\belly_obj_vr.dm" -#include "modular_citadel\code\modules\vore\eating\bellymodes_vr.dm" -#include "modular_citadel\code\modules\vore\eating\digest_act_vr.dm" -#include "modular_citadel\code\modules\vore\eating\living_vr.dm" -#include "modular_citadel\code\modules\vore\eating\vore_vr.dm" -#include "modular_citadel\code\modules\vore\eating\voreitems.dm" -#include "modular_citadel\code\modules\vore\eating\vorepanel_vr.dm" -#include "modular_citadel\interface\skin.dmf" -#include "yogstation\code\modules\power\energyharvester.dm" -// END_INCLUDE + +// DM Environment file for tgstation.dme. +// All manual changes should be made outside the BEGIN_ and END_ blocks. +// New source code should be placed in .dm files: choose File/New --> Code File. +// BEGIN_INTERNALS +// END_INTERNALS + +// BEGIN_FILE_DIR +#define FILE_DIR . +// END_FILE_DIR + +// BEGIN_PREFERENCES +#define DEBUG +// END_PREFERENCES + +// BEGIN_INCLUDE +#include "_maps\_basemap.dm" +#include "code\_compile_options.dm" +#include "code\world.dm" +#include "code\__DEFINES\_globals.dm" +#include "code\__DEFINES\_protect.dm" +#include "code\__DEFINES\_tick.dm" +#include "code\__DEFINES\access.dm" +#include "code\__DEFINES\admin.dm" +#include "code\__DEFINES\antagonists.dm" +#include "code\__DEFINES\atmospherics.dm" +#include "code\__DEFINES\atom_hud.dm" +#include "code\__DEFINES\bsql.config.dm" +#include "code\__DEFINES\bsql.dm" +#include "code\__DEFINES\callbacks.dm" +#include "code\__DEFINES\cargo.dm" +#include "code\__DEFINES\cinematics.dm" +#include "code\__DEFINES\citadel_defines.dm" +#include "code\__DEFINES\cleaning.dm" +#include "code\__DEFINES\clockcult.dm" +#include "code\__DEFINES\colors.dm" +#include "code\__DEFINES\combat.dm" +#include "code\__DEFINES\components.dm" +#include "code\__DEFINES\configuration.dm" +#include "code\__DEFINES\construction.dm" +#include "code\__DEFINES\contracts.dm" +#include "code\__DEFINES\cult.dm" +#include "code\__DEFINES\diseases.dm" +#include "code\__DEFINES\DNA.dm" +#include "code\__DEFINES\events.dm" +#include "code\__DEFINES\exports.dm" +#include "code\__DEFINES\flags.dm" +#include "code\__DEFINES\food.dm" +#include "code\__DEFINES\footsteps.dm" +#include "code\__DEFINES\hud.dm" +#include "code\__DEFINES\integrated_electronics.dm" +#include "code\__DEFINES\interaction_flags.dm" +#include "code\__DEFINES\inventory.dm" +#include "code\__DEFINES\is_helpers.dm" +#include "code\__DEFINES\jobs.dm" +#include "code\__DEFINES\language.dm" +#include "code\__DEFINES\layers.dm" +#include "code\__DEFINES\lighting.dm" +#include "code\__DEFINES\logging.dm" +#include "code\__DEFINES\machines.dm" +#include "code\__DEFINES\maps.dm" +#include "code\__DEFINES\maths.dm" +#include "code\__DEFINES\MC.dm" +#include "code\__DEFINES\mecha.dm" +#include "code\__DEFINES\medal.dm" +#include "code\__DEFINES\melee.dm" +#include "code\__DEFINES\menu.dm" +#include "code\__DEFINES\misc.dm" +#include "code\__DEFINES\mobs.dm" +#include "code\__DEFINES\monkeys.dm" +#include "code\__DEFINES\move_force.dm" +#include "code\__DEFINES\movement.dm" +#include "code\__DEFINES\movespeed_modification.dm" +#include "code\__DEFINES\nanites.dm" +#include "code\__DEFINES\networks.dm" +#include "code\__DEFINES\obj_flags.dm" +#include "code\__DEFINES\pinpointers.dm" +#include "code\__DEFINES\pipe_construction.dm" +#include "code\__DEFINES\pool.dm" +#include "code\__DEFINES\preferences.dm" +#include "code\__DEFINES\profile.dm" +#include "code\__DEFINES\qdel.dm" +#include "code\__DEFINES\radiation.dm" +#include "code\__DEFINES\radio.dm" +#include "code\__DEFINES\reactions.dm" +#include "code\__DEFINES\reagents.dm" +#include "code\__DEFINES\reagents_specific_heat.dm" +#include "code\__DEFINES\research.dm" +#include "code\__DEFINES\robots.dm" +#include "code\__DEFINES\role_preferences.dm" +#include "code\__DEFINES\rust_g.config.dm" +#include "code\__DEFINES\rust_g.dm" +#include "code\__DEFINES\say.dm" +#include "code\__DEFINES\shuttles.dm" +#include "code\__DEFINES\sight.dm" +#include "code\__DEFINES\sound.dm" +#include "code\__DEFINES\spaceman_dmm.dm" +#include "code\__DEFINES\stat.dm" +#include "code\__DEFINES\stat_tracking.dm" +#include "code\__DEFINES\status_effects.dm" +#include "code\__DEFINES\subsystems.dm" +#include "code\__DEFINES\tgs.config.dm" +#include "code\__DEFINES\tgs.dm" +#include "code\__DEFINES\tgui.dm" +#include "code\__DEFINES\time.dm" +#include "code\__DEFINES\tools.dm" +#include "code\__DEFINES\traits.dm" +#include "code\__DEFINES\turf_flags.dm" +#include "code\__DEFINES\typeids.dm" +#include "code\__DEFINES\vehicles.dm" +#include "code\__DEFINES\voreconstants.dm" +#include "code\__DEFINES\vv.dm" +#include "code\__DEFINES\wall_dents.dm" +#include "code\__DEFINES\wires.dm" +#include "code\__DEFINES\dcs\signals.dm" +#include "code\__HELPERS\_cit_helpers.dm" +#include "code\__HELPERS\_lists.dm" +#include "code\__HELPERS\_logging.dm" +#include "code\__HELPERS\_string_lists.dm" +#include "code\__HELPERS\areas.dm" +#include "code\__HELPERS\AStar.dm" +#include "code\__HELPERS\cmp.dm" +#include "code\__HELPERS\dates.dm" +#include "code\__HELPERS\dna.dm" +#include "code\__HELPERS\files.dm" +#include "code\__HELPERS\game.dm" +#include "code\__HELPERS\global_lists.dm" +#include "code\__HELPERS\heap.dm" +#include "code\__HELPERS\icon_smoothing.dm" +#include "code\__HELPERS\icons.dm" +#include "code\__HELPERS\level_traits.dm" +#include "code\__HELPERS\matrices.dm" +#include "code\__HELPERS\mobs.dm" +#include "code\__HELPERS\mouse_control.dm" +#include "code\__HELPERS\names.dm" +#include "code\__HELPERS\priority_announce.dm" +#include "code\__HELPERS\pronouns.dm" +#include "code\__HELPERS\qdel.dm" +#include "code\__HELPERS\radiation.dm" +#include "code\__HELPERS\radio.dm" +#include "code\__HELPERS\reagents.dm" +#include "code\__HELPERS\roundend.dm" +#include "code\__HELPERS\sanitize_values.dm" +#include "code\__HELPERS\shell.dm" +#include "code\__HELPERS\stat_tracking.dm" +#include "code\__HELPERS\text.dm" +#include "code\__HELPERS\text_vr.dm" +#include "code\__HELPERS\time.dm" +#include "code\__HELPERS\type2type.dm" +#include "code\__HELPERS\type2type_vr.dm" +#include "code\__HELPERS\typelists.dm" +#include "code\__HELPERS\unsorted.dm" +#include "code\__HELPERS\view.dm" +#include "code\__HELPERS\sorts\__main.dm" +#include "code\__HELPERS\sorts\InsertSort.dm" +#include "code\__HELPERS\sorts\MergeSort.dm" +#include "code\__HELPERS\sorts\TimSort.dm" +#include "code\_globalvars\bitfields.dm" +#include "code\_globalvars\configuration.dm" +#include "code\_globalvars\game_modes.dm" +#include "code\_globalvars\genetics.dm" +#include "code\_globalvars\logging.dm" +#include "code\_globalvars\misc.dm" +#include "code\_globalvars\regexes.dm" +#include "code\_globalvars\lists\flavor_misc.dm" +#include "code\_globalvars\lists\maintenance_loot.dm" +#include "code\_globalvars\lists\mapping.dm" +#include "code\_globalvars\lists\medals.dm" +#include "code\_globalvars\lists\mobs.dm" +#include "code\_globalvars\lists\names.dm" +#include "code\_globalvars\lists\objects.dm" +#include "code\_globalvars\lists\poll_ignore.dm" +#include "code\_globalvars\lists\typecache.dm" +#include "code\_globalvars\lists\vending.dm" +#include "code\_js\byjax.dm" +#include "code\_js\menus.dm" +#include "code\_onclick\adjacent.dm" +#include "code\_onclick\ai.dm" +#include "code\_onclick\click.dm" +#include "code\_onclick\cyborg.dm" +#include "code\_onclick\drag_drop.dm" +#include "code\_onclick\item_attack.dm" +#include "code\_onclick\observer.dm" +#include "code\_onclick\other_mobs.dm" +#include "code\_onclick\overmind.dm" +#include "code\_onclick\telekinesis.dm" +#include "code\_onclick\hud\_defines.dm" +#include "code\_onclick\hud\action_button.dm" +#include "code\_onclick\hud\ai.dm" +#include "code\_onclick\hud\alert.dm" +#include "code\_onclick\hud\alien.dm" +#include "code\_onclick\hud\alien_larva.dm" +#include "code\_onclick\hud\blob_overmind.dm" +#include "code\_onclick\hud\blobbernauthud.dm" +#include "code\_onclick\hud\constructs.dm" +#include "code\_onclick\hud\credits.dm" +#include "code\_onclick\hud\devil.dm" +#include "code\_onclick\hud\drones.dm" +#include "code\_onclick\hud\fullscreen.dm" +#include "code\_onclick\hud\generic_dextrous.dm" +#include "code\_onclick\hud\ghost.dm" +#include "code\_onclick\hud\guardian.dm" +#include "code\_onclick\hud\hud.dm" +#include "code\_onclick\hud\hud_cit.dm" +#include "code\_onclick\hud\human.dm" +#include "code\_onclick\hud\monkey.dm" +#include "code\_onclick\hud\movable_screen_objects.dm" +#include "code\_onclick\hud\parallax.dm" +#include "code\_onclick\hud\picture_in_picture.dm" +#include "code\_onclick\hud\plane_master.dm" +#include "code\_onclick\hud\radial.dm" +#include "code\_onclick\hud\radial_persistent.dm" +#include "code\_onclick\hud\revenanthud.dm" +#include "code\_onclick\hud\robot.dm" +#include "code\_onclick\hud\screen_objects.dm" +#include "code\_onclick\hud\swarmer.dm" +#include "code\controllers\admin.dm" +#include "code\controllers\configuration_citadel.dm" +#include "code\controllers\controller.dm" +#include "code\controllers\failsafe.dm" +#include "code\controllers\globals.dm" +#include "code\controllers\hooks.dm" +#include "code\controllers\master.dm" +#include "code\controllers\subsystem.dm" +#include "code\controllers\configuration\config_entry.dm" +#include "code\controllers\configuration\configuration.dm" +#include "code\controllers\configuration\entries\comms.dm" +#include "code\controllers\configuration\entries\dbconfig.dm" +#include "code\controllers\configuration\entries\game_options.dm" +#include "code\controllers\configuration\entries\general.dm" +#include "code\controllers\subsystem\acid.dm" +#include "code\controllers\subsystem\adjacent_air.dm" +#include "code\controllers\subsystem\air.dm" +#include "code\controllers\subsystem\air_turfs.dm" +#include "code\controllers\subsystem\assets.dm" +#include "code\controllers\subsystem\atoms.dm" +#include "code\controllers\subsystem\augury.dm" +#include "code\controllers\subsystem\autotransfer.dm" +#include "code\controllers\subsystem\blackbox.dm" +#include "code\controllers\subsystem\chat.dm" +#include "code\controllers\subsystem\communications.dm" +#include "code\controllers\subsystem\dbcore.dm" +#include "code\controllers\subsystem\dcs.dm" +#include "code\controllers\subsystem\disease.dm" +#include "code\controllers\subsystem\events.dm" +#include "code\controllers\subsystem\fire_burning.dm" +#include "code\controllers\subsystem\garbage.dm" +#include "code\controllers\subsystem\icon_smooth.dm" +#include "code\controllers\subsystem\idlenpcpool.dm" +#include "code\controllers\subsystem\input.dm" +#include "code\controllers\subsystem\ipintel.dm" +#include "code\controllers\subsystem\item_spawning.dm" +#include "code\controllers\subsystem\job.dm" +#include "code\controllers\subsystem\jukeboxes.dm" +#include "code\controllers\subsystem\language.dm" +#include "code\controllers\subsystem\lighting.dm" +#include "code\controllers\subsystem\machines.dm" +#include "code\controllers\subsystem\mapping.dm" +#include "code\controllers\subsystem\medals.dm" +#include "code\controllers\subsystem\minor_mapping.dm" +#include "code\controllers\subsystem\mobs.dm" +#include "code\controllers\subsystem\moods.dm" +#include "code\controllers\subsystem\nightshift.dm" +#include "code\controllers\subsystem\npcpool.dm" +#include "code\controllers\subsystem\overlays.dm" +#include "code\controllers\subsystem\pai.dm" +#include "code\controllers\subsystem\parallax.dm" +#include "code\controllers\subsystem\pathfinder.dm" +#include "code\controllers\subsystem\persistence.dm" +#include "code\controllers\subsystem\ping.dm" +#include "code\controllers\subsystem\radiation.dm" +#include "code\controllers\subsystem\radio.dm" +#include "code\controllers\subsystem\research.dm" +#include "code\controllers\subsystem\server_maint.dm" +#include "code\controllers\subsystem\shuttle.dm" +#include "code\controllers\subsystem\spacedrift.dm" +#include "code\controllers\subsystem\stickyban.dm" +#include "code\controllers\subsystem\sun.dm" +#include "code\controllers\subsystem\tgui.dm" +#include "code\controllers\subsystem\throwing.dm" +#include "code\controllers\subsystem\ticker.dm" +#include "code\controllers\subsystem\time_track.dm" +#include "code\controllers\subsystem\timer.dm" +#include "code\controllers\subsystem\title.dm" +#include "code\controllers\subsystem\traumas.dm" +#include "code\controllers\subsystem\vis_overlays.dm" +#include "code\controllers\subsystem\vore.dm" +#include "code\controllers\subsystem\vote.dm" +#include "code\controllers\subsystem\weather.dm" +#include "code\controllers\subsystem\processing\chemistry.dm" +#include "code\controllers\subsystem\processing\circuit.dm" +#include "code\controllers\subsystem\processing\fastprocess.dm" +#include "code\controllers\subsystem\processing\fields.dm" +#include "code\controllers\subsystem\processing\nanites.dm" +#include "code\controllers\subsystem\processing\networks.dm" +#include "code\controllers\subsystem\processing\obj.dm" +#include "code\controllers\subsystem\processing\processing.dm" +#include "code\controllers\subsystem\processing\projectiles.dm" +#include "code\controllers\subsystem\processing\quirks.dm" +#include "code\controllers\subsystem\processing\wet_floors.dm" +#include "code\datums\action.dm" +#include "code\datums\ai_laws.dm" +#include "code\datums\armor.dm" +#include "code\datums\beam.dm" +#include "code\datums\browser.dm" +#include "code\datums\callback.dm" +#include "code\datums\chatmessage.dm" +#include "code\datums\cinematic.dm" +#include "code\datums\dash_weapon.dm" +#include "code\datums\datacore.dm" +#include "code\datums\datum.dm" +#include "code\datums\datumvars.dm" +#include "code\datums\dna.dm" +#include "code\datums\dog_fashion.dm" +#include "code\datums\embedding_behavior.dm" +#include "code\datums\emotes.dm" +#include "code\datums\ert.dm" +#include "code\datums\explosion.dm" +#include "code\datums\forced_movement.dm" +#include "code\datums\holocall.dm" +#include "code\datums\http.dm" +#include "code\datums\hud.dm" +#include "code\datums\map_config.dm" +#include "code\datums\martial.dm" +#include "code\datums\mind.dm" +#include "code\datums\mutable_appearance.dm" +#include "code\datums\numbered_display.dm" +#include "code\datums\outfit.dm" +#include "code\datums\position_point_vector.dm" +#include "code\datums\profiling.dm" +#include "code\datums\progressbar.dm" +#include "code\datums\radiation_wave.dm" +#include "code\datums\recipe.dm" +#include "code\datums\ruins.dm" +#include "code\datums\saymode.dm" +#include "code\datums\shuttles.dm" +#include "code\datums\soullink.dm" +#include "code\datums\spawners_menu.dm" +#include "code\datums\verbs.dm" +#include "code\datums\weakrefs.dm" +#include "code\datums\world_topic.dm" +#include "code\datums\actions\beam_rifle.dm" +#include "code\datums\actions\ninja.dm" +#include "code\datums\brain_damage\brain_trauma.dm" +#include "code\datums\brain_damage\hypnosis.dm" +#include "code\datums\brain_damage\imaginary_friend.dm" +#include "code\datums\brain_damage\mild.dm" +#include "code\datums\brain_damage\phobia.dm" +#include "code\datums\brain_damage\severe.dm" +#include "code\datums\brain_damage\special.dm" +#include "code\datums\brain_damage\split_personality.dm" +#include "code\datums\components\_component.dm" +#include "code\datums\components\anti_magic.dm" +#include "code\datums\components\armor_plate.dm" +#include "code\datums\components\bouncy.dm" +#include "code\datums\components\butchering.dm" +#include "code\datums\components\caltrop.dm" +#include "code\datums\components\chasm.dm" +#include "code\datums\components\construction.dm" +#include "code\datums\components\decal.dm" +#include "code\datums\components\earprotection.dm" +#include "code\datums\components\edit_complainer.dm" +#include "code\datums\components\empprotection.dm" +#include "code\datums\components\footstep.dm" +#include "code\datums\components\forced_gravity.dm" +#include "code\datums\components\infective.dm" +#include "code\datums\components\jousting.dm" +#include "code\datums\components\knockoff.dm" +#include "code\datums\components\lockon_aiming.dm" +#include "code\datums\components\magnetic_catch.dm" +#include "code\datums\components\material_container.dm" +#include "code\datums\components\mirage_border.dm" +#include "code\datums\components\mood.dm" +#include "code\datums\components\nanites.dm" +#include "code\datums\components\ntnet_interface.dm" +#include "code\datums\components\orbiter.dm" +#include "code\datums\components\paintable.dm" +#include "code\datums\components\rad_insulation.dm" +#include "code\datums\components\radioactive.dm" +#include "code\datums\components\remote_materials.dm" +#include "code\datums\components\riding.dm" +#include "code\datums\components\rotation.dm" +#include "code\datums\components\signal_redirect.dm" +#include "code\datums\components\sizzle.dm" +#include "code\datums\components\slippery.dm" +#include "code\datums\components\spawner.dm" +#include "code\datums\components\spooky.dm" +#include "code\datums\components\squeak.dm" +#include "code\datums\components\stationloving.dm" +#include "code\datums\components\swarming.dm" +#include "code\datums\components\thermite.dm" +#include "code\datums\components\uplink.dm" +#include "code\datums\components\wearertargeting.dm" +#include "code\datums\components\wet_floor.dm" +#include "code\datums\components\storage\storage.dm" +#include "code\datums\components\storage\concrete\_concrete.dm" +#include "code\datums\components\storage\concrete\bag_of_holding.dm" +#include "code\datums\components\storage\concrete\bluespace.dm" +#include "code\datums\components\storage\concrete\emergency.dm" +#include "code\datums\components\storage\concrete\implant.dm" +#include "code\datums\components\storage\concrete\pockets.dm" +#include "code\datums\components\storage\concrete\rped.dm" +#include "code\datums\components\storage\concrete\special.dm" +#include "code\datums\components\storage\concrete\stack.dm" +#include "code\datums\diseases\_disease.dm" +#include "code\datums\diseases\_MobProcs.dm" +#include "code\datums\diseases\anxiety.dm" +#include "code\datums\diseases\appendicitis.dm" +#include "code\datums\diseases\beesease.dm" +#include "code\datums\diseases\brainrot.dm" +#include "code\datums\diseases\cold.dm" +#include "code\datums\diseases\cold9.dm" +#include "code\datums\diseases\dna_spread.dm" +#include "code\datums\diseases\fake_gbs.dm" +#include "code\datums\diseases\flu.dm" +#include "code\datums\diseases\fluspanish.dm" +#include "code\datums\diseases\gbs.dm" +#include "code\datums\diseases\heart_failure.dm" +#include "code\datums\diseases\magnitis.dm" +#include "code\datums\diseases\parrotpossession.dm" +#include "code\datums\diseases\pierrot_throat.dm" +#include "code\datums\diseases\retrovirus.dm" +#include "code\datums\diseases\rhumba_beat.dm" +#include "code\datums\diseases\transformation.dm" +#include "code\datums\diseases\tuberculosis.dm" +#include "code\datums\diseases\wizarditis.dm" +#include "code\datums\diseases\advance\advance.dm" +#include "code\datums\diseases\advance\presets.dm" +#include "code\datums\diseases\advance\symptoms\beard.dm" +#include "code\datums\diseases\advance\symptoms\choking.dm" +#include "code\datums\diseases\advance\symptoms\confusion.dm" +#include "code\datums\diseases\advance\symptoms\cough.dm" +#include "code\datums\diseases\advance\symptoms\deafness.dm" +#include "code\datums\diseases\advance\symptoms\dizzy.dm" +#include "code\datums\diseases\advance\symptoms\fever.dm" +#include "code\datums\diseases\advance\symptoms\fire.dm" +#include "code\datums\diseases\advance\symptoms\flesh_eating.dm" +#include "code\datums\diseases\advance\symptoms\hallucigen.dm" +#include "code\datums\diseases\advance\symptoms\headache.dm" +#include "code\datums\diseases\advance\symptoms\heal.dm" +#include "code\datums\diseases\advance\symptoms\itching.dm" +#include "code\datums\diseases\advance\symptoms\nanites.dm" +#include "code\datums\diseases\advance\symptoms\narcolepsy.dm" +#include "code\datums\diseases\advance\symptoms\oxygen.dm" +#include "code\datums\diseases\advance\symptoms\sensory.dm" +#include "code\datums\diseases\advance\symptoms\shedding.dm" +#include "code\datums\diseases\advance\symptoms\shivering.dm" +#include "code\datums\diseases\advance\symptoms\skin.dm" +#include "code\datums\diseases\advance\symptoms\sneeze.dm" +#include "code\datums\diseases\advance\symptoms\species.dm" +#include "code\datums\diseases\advance\symptoms\symptoms.dm" +#include "code\datums\diseases\advance\symptoms\viral.dm" +#include "code\datums\diseases\advance\symptoms\vision.dm" +#include "code\datums\diseases\advance\symptoms\voice_change.dm" +#include "code\datums\diseases\advance\symptoms\vomit.dm" +#include "code\datums\diseases\advance\symptoms\weight.dm" +#include "code\datums\diseases\advance\symptoms\youth.dm" +#include "code\datums\elements\_element.dm" +#include "code\datums\elements\cleaning.dm" +#include "code\datums\elements\earhealing.dm" +#include "code\datums\elements\ghost_role_eligibility.dm" +#include "code\datums\elements\mob_holder.dm" +#include "code\datums\elements\swimming.dm" +#include "code\datums\elements\wuv.dm" +#include "code\datums\helper_datums\events.dm" +#include "code\datums\helper_datums\getrev.dm" +#include "code\datums\helper_datums\icon_snapshot.dm" +#include "code\datums\helper_datums\teleport.dm" +#include "code\datums\helper_datums\topic_input.dm" +#include "code\datums\looping_sounds\_looping_sound.dm" +#include "code\datums\looping_sounds\item_sounds.dm" +#include "code\datums\looping_sounds\machinery_sounds.dm" +#include "code\datums\looping_sounds\weather.dm" +#include "code\datums\martial\boxing.dm" +#include "code\datums\martial\cqc.dm" +#include "code\datums\martial\krav_maga.dm" +#include "code\datums\martial\mushpunch.dm" +#include "code\datums\martial\plasma_fist.dm" +#include "code\datums\martial\psychotic_brawl.dm" +#include "code\datums\martial\sleeping_carp.dm" +#include "code\datums\martial\wrestling.dm" +#include "code\datums\mood_events\beauty_events.dm" +#include "code\datums\mood_events\drink_events.dm" +#include "code\datums\mood_events\drug_events.dm" +#include "code\datums\mood_events\generic_negative_events.dm" +#include "code\datums\mood_events\generic_positive_events.dm" +#include "code\datums\mood_events\mood_event.dm" +#include "code\datums\mood_events\needs_events.dm" +#include "code\datums\mutations\_mutations.dm" +#include "code\datums\mutations\actions.dm" +#include "code\datums\mutations\antenna.dm" +#include "code\datums\mutations\body.dm" +#include "code\datums\mutations\chameleon.dm" +#include "code\datums\mutations\cold_resistance.dm" +#include "code\datums\mutations\combined.dm" +#include "code\datums\mutations\hulk.dm" +#include "code\datums\mutations\radioactive.dm" +#include "code\datums\mutations\sight.dm" +#include "code\datums\mutations\speech.dm" +#include "code\datums\mutations\telekinesis.dm" +#include "code\datums\ruins\lavaland.dm" +#include "code\datums\ruins\space.dm" +#include "code\datums\ruins\station.dm" +#include "code\datums\status_effects\buffs.dm" +#include "code\datums\status_effects\debuffs.dm" +#include "code\datums\status_effects\gas.dm" +#include "code\datums\status_effects\neutral.dm" +#include "code\datums\status_effects\status_effect.dm" +#include "code\datums\traits\_quirk.dm" +#include "code\datums\traits\good.dm" +#include "code\datums\traits\negative.dm" +#include "code\datums\traits\neutral.dm" +#include "code\datums\weather\weather.dm" +#include "code\datums\weather\weather_types\acid_rain.dm" +#include "code\datums\weather\weather_types\ash_storm.dm" +#include "code\datums\weather\weather_types\floor_is_lava.dm" +#include "code\datums\weather\weather_types\radiation_storm.dm" +#include "code\datums\weather\weather_types\snow_storm.dm" +#include "code\datums\wires\_wires.dm" +#include "code\datums\wires\airalarm.dm" +#include "code\datums\wires\airlock.dm" +#include "code\datums\wires\apc.dm" +#include "code\datums\wires\autolathe.dm" +#include "code\datums\wires\emitter.dm" +#include "code\datums\wires\explosive.dm" +#include "code\datums\wires\microwave.dm" +#include "code\datums\wires\mulebot.dm" +#include "code\datums\wires\particle_accelerator.dm" +#include "code\datums\wires\r_n_d.dm" +#include "code\datums\wires\radio.dm" +#include "code\datums\wires\robot.dm" +#include "code\datums\wires\suit_storage_unit.dm" +#include "code\datums\wires\syndicatebomb.dm" +#include "code\datums\wires\tesla_coil.dm" +#include "code\datums\wires\vending.dm" +#include "code\game\alternate_appearance.dm" +#include "code\game\atoms.dm" +#include "code\game\atoms_movable.dm" +#include "code\game\communications.dm" +#include "code\game\data_huds.dm" +#include "code\game\say.dm" +#include "code\game\shuttle_engines.dm" +#include "code\game\sound.dm" +#include "code\game\world.dm" +#include "code\game\area\ai_monitored.dm" +#include "code\game\area\areas.dm" +#include "code\game\area\Space_Station_13_areas.dm" +#include "code\game\area\areas\away_content.dm" +#include "code\game\area\areas\centcom.dm" +#include "code\game\area\areas\holodeck.dm" +#include "code\game\area\areas\mining.dm" +#include "code\game\area\areas\shuttles.dm" +#include "code\game\area\areas\ruins\_ruins.dm" +#include "code\game\area\areas\ruins\lavaland.dm" +#include "code\game\area\areas\ruins\space.dm" +#include "code\game\area\areas\ruins\templates.dm" +#include "code\game\gamemodes\events.dm" +#include "code\game\gamemodes\game_mode.dm" +#include "code\game\gamemodes\objective.dm" +#include "code\game\gamemodes\objective_items.dm" +#include "code\game\gamemodes\bloodsucker\bloodsucker.dm" +#include "code\game\gamemodes\bloodsucker\hunter.dm" +#include "code\game\gamemodes\brother\traitor_bro.dm" +#include "code\game\gamemodes\changeling\changeling.dm" +#include "code\game\gamemodes\changeling\traitor_chan.dm" +#include "code\game\gamemodes\clock_cult\clock_cult.dm" +#include "code\game\gamemodes\clown_ops\bananium_bomb.dm" +#include "code\game\gamemodes\clown_ops\clown_ops.dm" +#include "code\game\gamemodes\clown_ops\clown_weapons.dm" +#include "code\game\gamemodes\cult\cult.dm" +#include "code\game\gamemodes\devil\devil_game_mode.dm" +#include "code\game\gamemodes\devil\game_mode.dm" +#include "code\game\gamemodes\devil\objectives.dm" +#include "code\game\gamemodes\devil\devil agent\devil_agent.dm" +#include "code\game\gamemodes\dynamic\dynamic.dm" +#include "code\game\gamemodes\dynamic\dynamic_rulesets.dm" +#include "code\game\gamemodes\dynamic\dynamic_rulesets_events.dm" +#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" +#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm" +#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm" +#include "code\game\gamemodes\extended\extended.dm" +#include "code\game\gamemodes\meteor\meteor.dm" +#include "code\game\gamemodes\meteor\meteors.dm" +#include "code\game\gamemodes\monkey\monkey.dm" +#include "code\game\gamemodes\nuclear\nuclear.dm" +#include "code\game\gamemodes\overthrow\objective.dm" +#include "code\game\gamemodes\overthrow\overthrow.dm" +#include "code\game\gamemodes\revolution\revolution.dm" +#include "code\game\gamemodes\sandbox\airlock_maker.dm" +#include "code\game\gamemodes\sandbox\h_sandbox.dm" +#include "code\game\gamemodes\sandbox\sandbox.dm" +#include "code\game\gamemodes\traitor\double_agents.dm" +#include "code\game\gamemodes\traitor\traitor.dm" +#include "code\game\gamemodes\wizard\wizard.dm" +#include "code\game\machinery\_machinery.dm" +#include "code\game\machinery\ai_slipper.dm" +#include "code\game\machinery\airlock_control.dm" +#include "code\game\machinery\announcement_system.dm" +#include "code\game\machinery\aug_manipulator.dm" +#include "code\game\machinery\autolathe.dm" +#include "code\game\machinery\bank_machine.dm" +#include "code\game\machinery\Beacon.dm" +#include "code\game\machinery\bloodbankgen.dm" +#include "code\game\machinery\buttons.dm" +#include "code\game\machinery\cell_charger.dm" +#include "code\game\machinery\cloning.dm" +#include "code\game\machinery\constructable_frame.dm" +#include "code\game\machinery\dance_machine.dm" +#include "code\game\machinery\defibrillator_mount.dm" +#include "code\game\machinery\deployable.dm" +#include "code\game\machinery\dish_drive.dm" +#include "code\game\machinery\dna_scanner.dm" +#include "code\game\machinery\doppler_array.dm" +#include "code\game\machinery\droneDispenser.dm" +#include "code\game\machinery\exp_cloner.dm" +#include "code\game\machinery\firealarm.dm" +#include "code\game\machinery\flasher.dm" +#include "code\game\machinery\gulag_item_reclaimer.dm" +#include "code\game\machinery\gulag_teleporter.dm" +#include "code\game\machinery\harvester.dm" +#include "code\game\machinery\hologram.dm" +#include "code\game\machinery\igniter.dm" +#include "code\game\machinery\iv_drip.dm" +#include "code\game\machinery\launch_pad.dm" +#include "code\game\machinery\lightswitch.dm" +#include "code\game\machinery\limbgrower.dm" +#include "code\game\machinery\magnet.dm" +#include "code\game\machinery\mass_driver.dm" +#include "code\game\machinery\navbeacon.dm" +#include "code\game\machinery\newscaster.dm" +#include "code\game\machinery\PDApainter.dm" +#include "code\game\machinery\posi_alert.dm" +#include "code\game\machinery\quantum_pad.dm" +#include "code\game\machinery\recharger.dm" +#include "code\game\machinery\rechargestation.dm" +#include "code\game\machinery\recycler.dm" +#include "code\game\machinery\requests_console.dm" +#include "code\game\machinery\shieldgen.dm" +#include "code\game\machinery\Sleeper.dm" +#include "code\game\machinery\slotmachine.dm" +#include "code\game\machinery\spaceheater.dm" +#include "code\game\machinery\stasis.dm" +#include "code\game\machinery\status_display.dm" +#include "code\game\machinery\suit_storage_unit.dm" +#include "code\game\machinery\syndicatebeacon.dm" +#include "code\game\machinery\syndicatebomb.dm" +#include "code\game\machinery\teleporter.dm" +#include "code\game\machinery\transformer.dm" +#include "code\game\machinery\washing_machine.dm" +#include "code\game\machinery\wishgranter.dm" +#include "code\game\machinery\camera\camera.dm" +#include "code\game\machinery\camera\camera_assembly.dm" +#include "code\game\machinery\camera\motion.dm" +#include "code\game\machinery\camera\presets.dm" +#include "code\game\machinery\camera\tracking.dm" +#include "code\game\machinery\computer\_computer.dm" +#include "code\game\machinery\computer\aifixer.dm" +#include "code\game\machinery\computer\apc_control.dm" +#include "code\game\machinery\computer\arcade.dm" +#include "code\game\machinery\computer\atmos_alert.dm" +#include "code\game\machinery\computer\atmos_control.dm" +#include "code\game\machinery\computer\buildandrepair.dm" +#include "code\game\machinery\computer\camera.dm" +#include "code\game\machinery\computer\camera_advanced.dm" +#include "code\game\machinery\computer\card.dm" +#include "code\game\machinery\computer\cloning.dm" +#include "code\game\machinery\computer\communications.dm" +#include "code\game\machinery\computer\crew.dm" +#include "code\game\machinery\computer\dna_console.dm" +#include "code\game\machinery\computer\launchpad_control.dm" +#include "code\game\machinery\computer\law.dm" +#include "code\game\machinery\computer\medical.dm" +#include "code\game\machinery\computer\Operating.dm" +#include "code\game\machinery\computer\pod.dm" +#include "code\game\machinery\computer\robot.dm" +#include "code\game\machinery\computer\security.dm" +#include "code\game\machinery\computer\station_alert.dm" +#include "code\game\machinery\computer\telecrystalconsoles.dm" +#include "code\game\machinery\computer\teleporter.dm" +#include "code\game\machinery\computer\arcade\battle.dm" +#include "code\game\machinery\computer\arcade\minesweeper.dm" +#include "code\game\machinery\computer\arcade\misc_arcade.dm" +#include "code\game\machinery\computer\arcade\orion_trail.dm" +#include "code\game\machinery\computer\prisoner\_prisoner.dm" +#include "code\game\machinery\computer\prisoner\gulag_teleporter.dm" +#include "code\game\machinery\computer\prisoner\management.dm" +#include "code\game\machinery\doors\airlock.dm" +#include "code\game\machinery\doors\airlock_electronics.dm" +#include "code\game\machinery\doors\airlock_types.dm" +#include "code\game\machinery\doors\alarmlock.dm" +#include "code\game\machinery\doors\brigdoors.dm" +#include "code\game\machinery\doors\checkForMultipleDoors.dm" +#include "code\game\machinery\doors\door.dm" +#include "code\game\machinery\doors\firedoor.dm" +#include "code\game\machinery\doors\passworddoor.dm" +#include "code\game\machinery\doors\poddoor.dm" +#include "code\game\machinery\doors\shutters.dm" +#include "code\game\machinery\doors\unpowered.dm" +#include "code\game\machinery\doors\windowdoor.dm" +#include "code\game\machinery\embedded_controller\access_controller.dm" +#include "code\game\machinery\embedded_controller\airlock_controller.dm" +#include "code\game\machinery\embedded_controller\embedded_controller_base.dm" +#include "code\game\machinery\embedded_controller\simple_vent_controller.dm" +#include "code\game\machinery\pipe\construction.dm" +#include "code\game\machinery\pipe\pipe_dispenser.dm" +#include "code\game\machinery\porta_turret\portable_turret.dm" +#include "code\game\machinery\porta_turret\portable_turret_construct.dm" +#include "code\game\machinery\porta_turret\portable_turret_cover.dm" +#include "code\game\machinery\poweredfans\fan_assembly.dm" +#include "code\game\machinery\poweredfans\poweredfans.dm" +#include "code\game\machinery\telecomms\broadcasting.dm" +#include "code\game\machinery\telecomms\machine_interactions.dm" +#include "code\game\machinery\telecomms\telecomunications.dm" +#include "code\game\machinery\telecomms\computers\logbrowser.dm" +#include "code\game\machinery\telecomms\computers\message.dm" +#include "code\game\machinery\telecomms\computers\telemonitor.dm" +#include "code\game\machinery\telecomms\machines\allinone.dm" +#include "code\game\machinery\telecomms\machines\broadcaster.dm" +#include "code\game\machinery\telecomms\machines\bus.dm" +#include "code\game\machinery\telecomms\machines\hub.dm" +#include "code\game\machinery\telecomms\machines\message_server.dm" +#include "code\game\machinery\telecomms\machines\processor.dm" +#include "code\game\machinery\telecomms\machines\receiver.dm" +#include "code\game\machinery\telecomms\machines\relay.dm" +#include "code\game\machinery\telecomms\machines\server.dm" +#include "code\game\mecha\mech_bay.dm" +#include "code\game\mecha\mech_fabricator.dm" +#include "code\game\mecha\mecha.dm" +#include "code\game\mecha\mecha_actions.dm" +#include "code\game\mecha\mecha_construction_paths.dm" +#include "code\game\mecha\mecha_control_console.dm" +#include "code\game\mecha\mecha_defense.dm" +#include "code\game\mecha\mecha_parts.dm" +#include "code\game\mecha\mecha_topic.dm" +#include "code\game\mecha\mecha_wreckage.dm" +#include "code\game\mecha\combat\combat.dm" +#include "code\game\mecha\combat\durand.dm" +#include "code\game\mecha\combat\gygax.dm" +#include "code\game\mecha\combat\honker.dm" +#include "code\game\mecha\combat\marauder.dm" +#include "code\game\mecha\combat\neovgre.dm" +#include "code\game\mecha\combat\phazon.dm" +#include "code\game\mecha\combat\reticence.dm" +#include "code\game\mecha\equipment\mecha_equipment.dm" +#include "code\game\mecha\equipment\tools\medical_tools.dm" +#include "code\game\mecha\equipment\tools\mining_tools.dm" +#include "code\game\mecha\equipment\tools\other_tools.dm" +#include "code\game\mecha\equipment\tools\work_tools.dm" +#include "code\game\mecha\equipment\weapons\weapons.dm" +#include "code\game\mecha\medical\medical.dm" +#include "code\game\mecha\medical\odysseus.dm" +#include "code\game\mecha\working\ripley.dm" +#include "code\game\mecha\working\working.dm" +#include "code\game\objects\buckling.dm" +#include "code\game\objects\empulse.dm" +#include "code\game\objects\items.dm" +#include "code\game\objects\obj_defense.dm" +#include "code\game\objects\objs.dm" +#include "code\game\objects\structures.dm" +#include "code\game\objects\effects\alien_acid.dm" +#include "code\game\objects\effects\anomalies.dm" +#include "code\game\objects\effects\blessing.dm" +#include "code\game\objects\effects\bump_teleporter.dm" +#include "code\game\objects\effects\contraband.dm" +#include "code\game\objects\effects\countdown.dm" +#include "code\game\objects\effects\effects.dm" +#include "code\game\objects\effects\forcefields.dm" +#include "code\game\objects\effects\glowshroom.dm" +#include "code\game\objects\effects\landmarks.dm" +#include "code\game\objects\effects\mines.dm" +#include "code\game\objects\effects\misc.dm" +#include "code\game\objects\effects\overlays.dm" +#include "code\game\objects\effects\portals.dm" +#include "code\game\objects\effects\proximity.dm" +#include "code\game\objects\effects\spiders.dm" +#include "code\game\objects\effects\step_triggers.dm" +#include "code\game\objects\effects\wanted_poster.dm" +#include "code\game\objects\effects\decals\cleanable.dm" +#include "code\game\objects\effects\decals\crayon.dm" +#include "code\game\objects\effects\decals\decal.dm" +#include "code\game\objects\effects\decals\misc.dm" +#include "code\game\objects\effects\decals\remains.dm" +#include "code\game\objects\effects\decals\cleanable\aliens.dm" +#include "code\game\objects\effects\decals\cleanable\gibs.dm" +#include "code\game\objects\effects\decals\cleanable\humans.dm" +#include "code\game\objects\effects\decals\cleanable\misc.dm" +#include "code\game\objects\effects\decals\cleanable\robots.dm" +#include "code\game\objects\effects\decals\turfdecal\dirt.dm" +#include "code\game\objects\effects\decals\turfdecal\markings.dm" +#include "code\game\objects\effects\decals\turfdecal\tilecoloring.dm" +#include "code\game\objects\effects\decals\turfdecal\weather.dm" +#include "code\game\objects\effects\effect_system\effect_system.dm" +#include "code\game\objects\effects\effect_system\effects_explosion.dm" +#include "code\game\objects\effects\effect_system\effects_foam.dm" +#include "code\game\objects\effects\effect_system\effects_other.dm" +#include "code\game\objects\effects\effect_system\effects_smoke.dm" +#include "code\game\objects\effects\effect_system\effects_sparks.dm" +#include "code\game\objects\effects\effect_system\effects_water.dm" +#include "code\game\objects\effects\spawners\bombspawner.dm" +#include "code\game\objects\effects\spawners\bundle.dm" +#include "code\game\objects\effects\spawners\gibspawner.dm" +#include "code\game\objects\effects\spawners\lootdrop.dm" +#include "code\game\objects\effects\spawners\structure.dm" +#include "code\game\objects\effects\spawners\traps.dm" +#include "code\game\objects\effects\spawners\vaultspawner.dm" +#include "code\game\objects\effects\spawners\xeno_egg_delivery.dm" +#include "code\game\objects\effects\temporary_visuals\clockcult.dm" +#include "code\game\objects\effects\temporary_visuals\cult.dm" +#include "code\game\objects\effects\temporary_visuals\miscellaneous.dm" +#include "code\game\objects\effects\temporary_visuals\temporary_visual.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\impact.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\muzzle.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\projectile_effects.dm" +#include "code\game\objects\effects\temporary_visuals\projectiles\tracer.dm" +#include "code\game\objects\items\AI_modules.dm" +#include "code\game\objects\items\airlock_painter.dm" +#include "code\game\objects\items\apc_frame.dm" +#include "code\game\objects\items\blueprints.dm" +#include "code\game\objects\items\body_egg.dm" +#include "code\game\objects\items\bodybag.dm" +#include "code\game\objects\items\candle.dm" +#include "code\game\objects\items\cardboard_cutouts.dm" +#include "code\game\objects\items\cards_ids.dm" +#include "code\game\objects\items\charter.dm" +#include "code\game\objects\items\chromosome.dm" +#include "code\game\objects\items\chrono_eraser.dm" +#include "code\game\objects\items\cigs_lighters.dm" +#include "code\game\objects\items\clown_items.dm" +#include "code\game\objects\items\control_wand.dm" +#include "code\game\objects\items\cosmetics.dm" +#include "code\game\objects\items\courtroom.dm" +#include "code\game\objects\items\crayons.dm" +#include "code\game\objects\items\defib.dm" +#include "code\game\objects\items\dehy_carp.dm" +#include "code\game\objects\items\dice.dm" +#include "code\game\objects\items\dna_injector.dm" +#include "code\game\objects\items\documents.dm" +#include "code\game\objects\items\eightball.dm" +#include "code\game\objects\items\extinguisher.dm" +#include "code\game\objects\items\flamethrower.dm" +#include "code\game\objects\items\gift.dm" +#include "code\game\objects\items\granters.dm" +#include "code\game\objects\items\handcuffs.dm" +#include "code\game\objects\items\his_grace.dm" +#include "code\game\objects\items\holosign_creator.dm" +#include "code\game\objects\items\holy_weapons.dm" +#include "code\game\objects\items\hot_potato.dm" +#include "code\game\objects\items\inducer.dm" +#include "code\game\objects\items\kitchen.dm" +#include "code\game\objects\items\latexballoon.dm" +#include "code\game\objects\items\lorebooks.dm" +#include "code\game\objects\items\manuals.dm" +#include "code\game\objects\items\mesmetron.dm" +#include "code\game\objects\items\miscellaneous.dm" +#include "code\game\objects\items\mop.dm" +#include "code\game\objects\items\paint.dm" +#include "code\game\objects\items\paiwire.dm" +#include "code\game\objects\items\pet_carrier.dm" +#include "code\game\objects\items\pinpointer.dm" +#include "code\game\objects\items\plushes.dm" +#include "code\game\objects\items\pneumaticCannon.dm" +#include "code\game\objects\items\powerfist.dm" +#include "code\game\objects\items\RCD.dm" +#include "code\game\objects\items\RCL.dm" +#include "code\game\objects\items\religion.dm" +#include "code\game\objects\items\RPD.dm" +#include "code\game\objects\items\RSF.dm" +#include "code\game\objects\items\scrolls.dm" +#include "code\game\objects\items\sharpener.dm" +#include "code\game\objects\items\shields.dm" +#include "code\game\objects\items\shooting_range.dm" +#include "code\game\objects\items\signs.dm" +#include "code\game\objects\items\singularityhammer.dm" +#include "code\game\objects\items\stunbaton.dm" +#include "code\game\objects\items\taster.dm" +#include "code\game\objects\items\teleportation.dm" +#include "code\game\objects\items\teleprod.dm" +#include "code\game\objects\items\theft_tools.dm" +#include "code\game\objects\items\toys.dm" +#include "code\game\objects\items\trash.dm" +#include "code\game\objects\items\twohanded.dm" +#include "code\game\objects\items\vending_items.dm" +#include "code\game\objects\items\weaponry.dm" +#include "code\game\objects\items\circuitboards\circuitboard.dm" +#include "code\game\objects\items\circuitboards\computer_circuitboards.dm" +#include "code\game\objects\items\circuitboards\machine_circuitboards.dm" +#include "code\game\objects\items\devices\aicard.dm" +#include "code\game\objects\items\devices\anomaly_neutralizer.dm" +#include "code\game\objects\items\devices\beacon.dm" +#include "code\game\objects\items\devices\camera_bug.dm" +#include "code\game\objects\items\devices\chameleonproj.dm" +#include "code\game\objects\items\devices\compressionkit.dm" +#include "code\game\objects\items\devices\desynchronizer.dm" +#include "code\game\objects\items\devices\dogborg_sleeper.dm" +#include "code\game\objects\items\devices\doorCharge.dm" +#include "code\game\objects\items\devices\electroadaptive_pseudocircuit.dm" +#include "code\game\objects\items\devices\flashlight.dm" +#include "code\game\objects\items\devices\forcefieldprojector.dm" +#include "code\game\objects\items\devices\geiger_counter.dm" +#include "code\game\objects\items\devices\glue.dm" +#include "code\game\objects\items\devices\gps.dm" +#include "code\game\objects\items\devices\instruments.dm" +#include "code\game\objects\items\devices\laserpointer.dm" +#include "code\game\objects\items\devices\lightreplacer.dm" +#include "code\game\objects\items\devices\megaphone.dm" +#include "code\game\objects\items\devices\multitool.dm" +#include "code\game\objects\items\devices\paicard.dm" +#include "code\game\objects\items\devices\pipe_painter.dm" +#include "code\game\objects\items\devices\powersink.dm" +#include "code\game\objects\items\devices\pressureplates.dm" +#include "code\game\objects\items\devices\quantum_keycard.dm" +#include "code\game\objects\items\devices\reverse_bear_trap.dm" +#include "code\game\objects\items\devices\scanners.dm" +#include "code\game\objects\items\devices\sensor_device.dm" +#include "code\game\objects\items\devices\taperecorder.dm" +#include "code\game\objects\items\devices\traitordevices.dm" +#include "code\game\objects\items\devices\transfer_valve.dm" +#include "code\game\objects\items\devices\PDA\cart.dm" +#include "code\game\objects\items\devices\PDA\PDA.dm" +#include "code\game\objects\items\devices\PDA\PDA_types.dm" +#include "code\game\objects\items\devices\PDA\radio.dm" +#include "code\game\objects\items\devices\PDA\virus_cart.dm" +#include "code\game\objects\items\devices\radio\electropack.dm" +#include "code\game\objects\items\devices\radio\encryptionkey.dm" +#include "code\game\objects\items\devices\radio\headset.dm" +#include "code\game\objects\items\devices\radio\intercom.dm" +#include "code\game\objects\items\devices\radio\radio.dm" +#include "code\game\objects\items\grenades\antigravity.dm" +#include "code\game\objects\items\grenades\chem_grenade.dm" +#include "code\game\objects\items\grenades\clusterbuster.dm" +#include "code\game\objects\items\grenades\emgrenade.dm" +#include "code\game\objects\items\grenades\flashbang.dm" +#include "code\game\objects\items\grenades\ghettobomb.dm" +#include "code\game\objects\items\grenades\grenade.dm" +#include "code\game\objects\items\grenades\plastic.dm" +#include "code\game\objects\items\grenades\smokebomb.dm" +#include "code\game\objects\items\grenades\spawnergrenade.dm" +#include "code\game\objects\items\grenades\syndieminibomb.dm" +#include "code\game\objects\items\implants\implant.dm" +#include "code\game\objects\items\implants\implant_abductor.dm" +#include "code\game\objects\items\implants\implant_chem.dm" +#include "code\game\objects\items\implants\implant_clown.dm" +#include "code\game\objects\items\implants\implant_exile.dm" +#include "code\game\objects\items\implants\implant_explosive.dm" +#include "code\game\objects\items\implants\implant_freedom.dm" +#include "code\game\objects\items\implants\implant_krav_maga.dm" +#include "code\game\objects\items\implants\implant_mindshield.dm" +#include "code\game\objects\items\implants\implant_misc.dm" +#include "code\game\objects\items\implants\implant_radio.dm" +#include "code\game\objects\items\implants\implant_slave.dm" +#include "code\game\objects\items\implants\implant_spell.dm" +#include "code\game\objects\items\implants\implant_stealth.dm" +#include "code\game\objects\items\implants\implant_storage.dm" +#include "code\game\objects\items\implants\implant_track.dm" +#include "code\game\objects\items\implants\implant_uplink.dm" +#include "code\game\objects\items\implants\implantcase.dm" +#include "code\game\objects\items\implants\implantchair.dm" +#include "code\game\objects\items\implants\implanter.dm" +#include "code\game\objects\items\implants\implantpad.dm" +#include "code\game\objects\items\melee\energy.dm" +#include "code\game\objects\items\melee\misc.dm" +#include "code\game\objects\items\melee\transforming.dm" +#include "code\game\objects\items\robot\ai_upgrades.dm" +#include "code\game\objects\items\robot\robot_items.dm" +#include "code\game\objects\items\robot\robot_parts.dm" +#include "code\game\objects\items\robot\robot_upgrades.dm" +#include "code\game\objects\items\stacks\bscrystal.dm" +#include "code\game\objects\items\stacks\cash.dm" +#include "code\game\objects\items\stacks\medical.dm" +#include "code\game\objects\items\stacks\rods.dm" +#include "code\game\objects\items\stacks\stack.dm" +#include "code\game\objects\items\stacks\telecrystal.dm" +#include "code\game\objects\items\stacks\wrap.dm" +#include "code\game\objects\items\stacks\sheets\glass.dm" +#include "code\game\objects\items\stacks\sheets\leather.dm" +#include "code\game\objects\items\stacks\sheets\light.dm" +#include "code\game\objects\items\stacks\sheets\mineral.dm" +#include "code\game\objects\items\stacks\sheets\sheet_types.dm" +#include "code\game\objects\items\stacks\sheets\sheets.dm" +#include "code\game\objects\items\stacks\tiles\light.dm" +#include "code\game\objects\items\stacks\tiles\tile_mineral.dm" +#include "code\game\objects\items\stacks\tiles\tile_reskinning.dm" +#include "code\game\objects\items\stacks\tiles\tile_types.dm" +#include "code\game\objects\items\storage\backpack.dm" +#include "code\game\objects\items\storage\bags.dm" +#include "code\game\objects\items\storage\belt.dm" +#include "code\game\objects\items\storage\book.dm" +#include "code\game\objects\items\storage\boxes.dm" +#include "code\game\objects\items\storage\briefcase.dm" +#include "code\game\objects\items\storage\dakis.dm" +#include "code\game\objects\items\storage\fancy.dm" +#include "code\game\objects\items\storage\firstaid.dm" +#include "code\game\objects\items\storage\lockbox.dm" +#include "code\game\objects\items\storage\secure.dm" +#include "code\game\objects\items\storage\storage.dm" +#include "code\game\objects\items\storage\toolbox.dm" +#include "code\game\objects\items\storage\uplink_kits.dm" +#include "code\game\objects\items\storage\wallets.dm" +#include "code\game\objects\items\tanks\jetpack.dm" +#include "code\game\objects\items\tanks\tank_types.dm" +#include "code\game\objects\items\tanks\tanks.dm" +#include "code\game\objects\items\tanks\watertank.dm" +#include "code\game\objects\items\tools\crowbar.dm" +#include "code\game\objects\items\tools\screwdriver.dm" +#include "code\game\objects\items\tools\weldingtool.dm" +#include "code\game\objects\items\tools\wirecutters.dm" +#include "code\game\objects\items\tools\wrench.dm" +#include "code\game\objects\structures\ai_core.dm" +#include "code\game\objects\structures\aliens.dm" +#include "code\game\objects\structures\artstuff.dm" +#include "code\game\objects\structures\barsigns.dm" +#include "code\game\objects\structures\bedsheet_bin.dm" +#include "code\game\objects\structures\destructible_structures.dm" +#include "code\game\objects\structures\displaycase.dm" +#include "code\game\objects\structures\divine.dm" +#include "code\game\objects\structures\door_assembly.dm" +#include "code\game\objects\structures\door_assembly_types.dm" +#include "code\game\objects\structures\dresser.dm" +#include "code\game\objects\structures\electricchair.dm" +#include "code\game\objects\structures\extinguisher.dm" +#include "code\game\objects\structures\false_walls.dm" +#include "code\game\objects\structures\fence.dm" +#include "code\game\objects\structures\fireaxe.dm" +#include "code\game\objects\structures\fireplace.dm" +#include "code\game\objects\structures\flora.dm" +#include "code\game\objects\structures\fluff.dm" +#include "code\game\objects\structures\ghost_role_spawners.dm" +#include "code\game\objects\structures\girders.dm" +#include "code\game\objects\structures\grille.dm" +#include "code\game\objects\structures\guillotine.dm" +#include "code\game\objects\structures\guncase.dm" +#include "code\game\objects\structures\headpike.dm" +#include "code\game\objects\structures\hivebot.dm" +#include "code\game\objects\structures\holosign.dm" +#include "code\game\objects\structures\janicart.dm" +#include "code\game\objects\structures\kitchen_spike.dm" +#include "code\game\objects\structures\ladders.dm" +#include "code\game\objects\structures\lattice.dm" +#include "code\game\objects\structures\life_candle.dm" +#include "code\game\objects\structures\loom.dm" +#include "code\game\objects\structures\manned_turret.dm" +#include "code\game\objects\structures\medkit.dm" +#include "code\game\objects\structures\memorial.dm" +#include "code\game\objects\structures\mineral_doors.dm" +#include "code\game\objects\structures\mirror.dm" +#include "code\game\objects\structures\mop_bucket.dm" +#include "code\game\objects\structures\morgue.dm" +#include "code\game\objects\structures\musician.dm" +#include "code\game\objects\structures\noticeboard.dm" +#include "code\game\objects\structures\petrified_statue.dm" +#include "code\game\objects\structures\plasticflaps.dm" +#include "code\game\objects\structures\reflector.dm" +#include "code\game\objects\structures\safe.dm" +#include "code\game\objects\structures\showcase.dm" +#include "code\game\objects\structures\spawner.dm" +#include "code\game\objects\structures\spirit_board.dm" +#include "code\game\objects\structures\stairs.dm" +#include "code\game\objects\structures\statues.dm" +#include "code\game\objects\structures\table_frames.dm" +#include "code\game\objects\structures\tables_racks.dm" +#include "code\game\objects\structures\tank_dispenser.dm" +#include "code\game\objects\structures\target_stake.dm" +#include "code\game\objects\structures\traps.dm" +#include "code\game\objects\structures\trash_piles.dm" +#include "code\game\objects\structures\watercloset.dm" +#include "code\game\objects\structures\windoor_assembly.dm" +#include "code\game\objects\structures\window.dm" +#include "code\game\objects\structures\beds_chairs\alien_nest.dm" +#include "code\game\objects\structures\beds_chairs\bed.dm" +#include "code\game\objects\structures\beds_chairs\chair.dm" +#include "code\game\objects\structures\crates_lockers\closets.dm" +#include "code\game\objects\structures\crates_lockers\crates.dm" +#include "code\game\objects\structures\crates_lockers\closets\bodybag.dm" +#include "code\game\objects\structures\crates_lockers\closets\cardboardbox.dm" +#include "code\game\objects\structures\crates_lockers\closets\fitness.dm" +#include "code\game\objects\structures\crates_lockers\closets\gimmick.dm" +#include "code\game\objects\structures\crates_lockers\closets\job_closets.dm" +#include "code\game\objects\structures\crates_lockers\closets\l3closet.dm" +#include "code\game\objects\structures\crates_lockers\closets\syndicate.dm" +#include "code\game\objects\structures\crates_lockers\closets\utility_closets.dm" +#include "code\game\objects\structures\crates_lockers\closets\wardrobe.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\bar.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\cargo.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\engineering.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\freezer.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\hydroponics.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\medical.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\misc.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\personal.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\scientist.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\secure_closets.dm" +#include "code\game\objects\structures\crates_lockers\closets\secure\security.dm" +#include "code\game\objects\structures\crates_lockers\crates\bins.dm" +#include "code\game\objects\structures\crates_lockers\crates\critter.dm" +#include "code\game\objects\structures\crates_lockers\crates\large.dm" +#include "code\game\objects\structures\crates_lockers\crates\secure.dm" +#include "code\game\objects\structures\crates_lockers\crates\wooden.dm" +#include "code\game\objects\structures\lavaland\necropolis_tendril.dm" +#include "code\game\objects\structures\signs\_signs.dm" +#include "code\game\objects\structures\signs\signs_departments.dm" +#include "code\game\objects\structures\signs\signs_maps.dm" +#include "code\game\objects\structures\signs\signs_plaques.dm" +#include "code\game\objects\structures\signs\signs_warning.dm" +#include "code\game\objects\structures\transit_tubes\station.dm" +#include "code\game\objects\structures\transit_tubes\transit_tube.dm" +#include "code\game\objects\structures\transit_tubes\transit_tube_construction.dm" +#include "code\game\objects\structures\transit_tubes\transit_tube_pod.dm" +#include "code\game\turfs\baseturf_skipover.dm" +#include "code\game\turfs\change_turf.dm" +#include "code\game\turfs\closed.dm" +#include "code\game\turfs\open.dm" +#include "code\game\turfs\turf.dm" +#include "code\game\turfs\openspace\openspace.dm" +#include "code\game\turfs\simulated\chasm.dm" +#include "code\game\turfs\simulated\dirtystation.dm" +#include "code\game\turfs\simulated\floor.dm" +#include "code\game\turfs\simulated\lava.dm" +#include "code\game\turfs\simulated\minerals.dm" +#include "code\game\turfs\simulated\reebe_void.dm" +#include "code\game\turfs\simulated\river.dm" +#include "code\game\turfs\simulated\walls.dm" +#include "code\game\turfs\simulated\water.dm" +#include "code\game\turfs\simulated\floor\fancy_floor.dm" +#include "code\game\turfs\simulated\floor\light_floor.dm" +#include "code\game\turfs\simulated\floor\mineral_floor.dm" +#include "code\game\turfs\simulated\floor\misc_floor.dm" +#include "code\game\turfs\simulated\floor\plasteel_floor.dm" +#include "code\game\turfs\simulated\floor\plating.dm" +#include "code\game\turfs\simulated\floor\reinf_floor.dm" +#include "code\game\turfs\simulated\floor\plating\asteroid.dm" +#include "code\game\turfs\simulated\floor\plating\dirt.dm" +#include "code\game\turfs\simulated\floor\plating\misc_plating.dm" +#include "code\game\turfs\simulated\wall\mineral_walls.dm" +#include "code\game\turfs\simulated\wall\misc_walls.dm" +#include "code\game\turfs\simulated\wall\reinf_walls.dm" +#include "code\game\turfs\space\space.dm" +#include "code\game\turfs\space\transit.dm" +#include "code\modules\admin\admin.dm" +#include "code\modules\admin\admin_investigate.dm" +#include "code\modules\admin\admin_ranks.dm" +#include "code\modules\admin\admin_verbs.dm" +#include "code\modules\admin\adminmenu.dm" +#include "code\modules\admin\antag_panel.dm" +#include "code\modules\admin\banjob.dm" +#include "code\modules\admin\chat_commands.dm" +#include "code\modules\admin\check_antagonists.dm" +#include "code\modules\admin\create_mob.dm" +#include "code\modules\admin\create_object.dm" +#include "code\modules\admin\create_poll.dm" +#include "code\modules\admin\create_turf.dm" +#include "code\modules\admin\fun_balloon.dm" +#include "code\modules\admin\holder2.dm" +#include "code\modules\admin\ipintel.dm" +#include "code\modules\admin\IsBanned.dm" +#include "code\modules\admin\NewBan.dm" +#include "code\modules\admin\permissionedit.dm" +#include "code\modules\admin\player_panel.dm" +#include "code\modules\admin\secrets.dm" +#include "code\modules\admin\sound_emitter.dm" +#include "code\modules\admin\sql_message_system.dm" +#include "code\modules\admin\stickyban.dm" +#include "code\modules\admin\topic.dm" +#include "code\modules\admin\whitelist.dm" +#include "code\modules\admin\DB_ban\functions.dm" +#include "code\modules\admin\verbs\adminhelp.dm" +#include "code\modules\admin\verbs\adminjump.dm" +#include "code\modules\admin\verbs\adminpm.dm" +#include "code\modules\admin\verbs\adminsay.dm" +#include "code\modules\admin\verbs\ak47s.dm" +#include "code\modules\admin\verbs\atmosdebug.dm" +#include "code\modules\admin\verbs\bluespacearty.dm" +#include "code\modules\admin\verbs\borgpanel.dm" +#include "code\modules\admin\verbs\BrokenInhands.dm" +#include "code\modules\admin\verbs\cinematic.dm" +#include "code\modules\admin\verbs\deadsay.dm" +#include "code\modules\admin\verbs\debug.dm" +#include "code\modules\admin\verbs\diagnostics.dm" +#include "code\modules\admin\verbs\dice.dm" +#include "code\modules\admin\verbs\fps.dm" +#include "code\modules\admin\verbs\getlogs.dm" +#include "code\modules\admin\verbs\individual_logging.dm" +#include "code\modules\admin\verbs\machine_upgrade.dm" +#include "code\modules\admin\verbs\manipulate_organs.dm" +#include "code\modules\admin\verbs\map_template_loadverb.dm" +#include "code\modules\admin\verbs\mapping.dm" +#include "code\modules\admin\verbs\maprotation.dm" +#include "code\modules\admin\verbs\massmodvar.dm" +#include "code\modules\admin\verbs\modifyvariables.dm" +#include "code\modules\admin\verbs\one_click_antag.dm" +#include "code\modules\admin\verbs\onlyone.dm" +#include "code\modules\admin\verbs\panicbunker.dm" +#include "code\modules\admin\verbs\playsound.dm" +#include "code\modules\admin\verbs\possess.dm" +#include "code\modules\admin\verbs\pray.dm" +#include "code\modules\admin\verbs\randomverbs.dm" +#include "code\modules\admin\verbs\reestablish_db_connection.dm" +#include "code\modules\admin\verbs\spawnfloorcluwne.dm" +#include "code\modules\admin\verbs\spawnobjasmob.dm" +#include "code\modules\admin\verbs\tripAI.dm" +#include "code\modules\admin\verbs\SDQL2\SDQL_2.dm" +#include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm" +#include "code\modules\admin\verbs\SDQL2\SDQL_2_wrappers.dm" +#include "code\modules\antagonists\_common\antag_datum.dm" +#include "code\modules\antagonists\_common\antag_helpers.dm" +#include "code\modules\antagonists\_common\antag_hud.dm" +#include "code\modules\antagonists\_common\antag_spawner.dm" +#include "code\modules\antagonists\_common\antag_team.dm" +#include "code\modules\antagonists\abductor\abductor.dm" +#include "code\modules\antagonists\abductor\abductee\abductee_objectives.dm" +#include "code\modules\antagonists\abductor\equipment\abduction_gear.dm" +#include "code\modules\antagonists\abductor\equipment\abduction_outfits.dm" +#include "code\modules\antagonists\abductor\equipment\abduction_surgery.dm" +#include "code\modules\antagonists\abductor\equipment\gland.dm" +#include "code\modules\antagonists\abductor\machinery\camera.dm" +#include "code\modules\antagonists\abductor\machinery\console.dm" +#include "code\modules\antagonists\abductor\machinery\dispenser.dm" +#include "code\modules\antagonists\abductor\machinery\experiment.dm" +#include "code\modules\antagonists\abductor\machinery\pad.dm" +#include "code\modules\antagonists\blob\blob.dm" +#include "code\modules\antagonists\blob\blob\blob_report.dm" +#include "code\modules\antagonists\blob\blob\overmind.dm" +#include "code\modules\antagonists\blob\blob\powers.dm" +#include "code\modules\antagonists\blob\blob\theblob.dm" +#include "code\modules\antagonists\blob\blob\blobs\blob_mobs.dm" +#include "code\modules\antagonists\blob\blob\blobs\core.dm" +#include "code\modules\antagonists\blob\blob\blobs\factory.dm" +#include "code\modules\antagonists\blob\blob\blobs\node.dm" +#include "code\modules\antagonists\blob\blob\blobs\resource.dm" +#include "code\modules\antagonists\blob\blob\blobs\shield.dm" +#include "code\modules\antagonists\bloodsucker\bloodsucker_flaws.dm" +#include "code\modules\antagonists\bloodsucker\bloodsucker_integration.dm" +#include "code\modules\antagonists\bloodsucker\bloodsucker_life.dm" +#include "code\modules\antagonists\bloodsucker\bloodsucker_objectives.dm" +#include "code\modules\antagonists\bloodsucker\bloodsucker_powers.dm" +#include "code\modules\antagonists\bloodsucker\bloodsucker_sunlight.dm" +#include "code\modules\antagonists\bloodsucker\bloodsucker_ui.dm" +#include "code\modules\antagonists\bloodsucker\datum_bloodsucker.dm" +#include "code\modules\antagonists\bloodsucker\datum_hunter.dm" +#include "code\modules\antagonists\bloodsucker\datum_vassal.dm" +#include "code\modules\antagonists\bloodsucker\items\bloodsucker_organs.dm" +#include "code\modules\antagonists\bloodsucker\items\bloodsucker_stake.dm" +#include "code\modules\antagonists\bloodsucker\objects\bloodsucker_coffin.dm" +#include "code\modules\antagonists\bloodsucker\objects\bloodsucker_crypt.dm" +#include "code\modules\antagonists\bloodsucker\objects\bloodsucker_lair.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_brawn.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_cloak.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_feed.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_fortitude.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_gohome.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_haste.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_lunge.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_masquerade.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_mesmerize.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_trespass.dm" +#include "code\modules\antagonists\bloodsucker\powers\bs_veil.dm" +#include "code\modules\antagonists\bloodsucker\powers\v_recuperate.dm" +#include "code\modules\antagonists\brainwashing\brainwashing.dm" +#include "code\modules\antagonists\brother\brother.dm" +#include "code\modules\antagonists\changeling\cellular_emporium.dm" +#include "code\modules\antagonists\changeling\changeling.dm" +#include "code\modules\antagonists\changeling\changeling_power.dm" +#include "code\modules\antagonists\changeling\powers\absorb.dm" +#include "code\modules\antagonists\changeling\powers\adrenaline.dm" +#include "code\modules\antagonists\changeling\powers\augmented_eyesight.dm" +#include "code\modules\antagonists\changeling\powers\biodegrade.dm" +#include "code\modules\antagonists\changeling\powers\chameleon_skin.dm" +#include "code\modules\antagonists\changeling\powers\digitalcamo.dm" +#include "code\modules\antagonists\changeling\powers\fakedeath.dm" +#include "code\modules\antagonists\changeling\powers\fleshmend.dm" +#include "code\modules\antagonists\changeling\powers\headcrab.dm" +#include "code\modules\antagonists\changeling\powers\hivemind.dm" +#include "code\modules\antagonists\changeling\powers\humanform.dm" +#include "code\modules\antagonists\changeling\powers\lesserform.dm" +#include "code\modules\antagonists\changeling\powers\linglink.dm" +#include "code\modules\antagonists\changeling\powers\mimic_voice.dm" +#include "code\modules\antagonists\changeling\powers\mutations.dm" +#include "code\modules\antagonists\changeling\powers\panacea.dm" +#include "code\modules\antagonists\changeling\powers\pheromone_receptors.dm" +#include "code\modules\antagonists\changeling\powers\regenerate.dm" +#include "code\modules\antagonists\changeling\powers\revive.dm" +#include "code\modules\antagonists\changeling\powers\shriek.dm" +#include "code\modules\antagonists\changeling\powers\spiders.dm" +#include "code\modules\antagonists\changeling\powers\strained_muscles.dm" +#include "code\modules\antagonists\changeling\powers\tiny_prick.dm" +#include "code\modules\antagonists\changeling\powers\transform.dm" +#include "code\modules\antagonists\clockcult\clock_effect.dm" +#include "code\modules\antagonists\clockcult\clock_item.dm" +#include "code\modules\antagonists\clockcult\clock_mobs.dm" +#include "code\modules\antagonists\clockcult\clock_scripture.dm" +#include "code\modules\antagonists\clockcult\clock_structure.dm" +#include "code\modules\antagonists\clockcult\clockcult.dm" +#include "code\modules\antagonists\clockcult\clock_effects\city_of_cogs_rift.dm" +#include "code\modules\antagonists\clockcult\clock_effects\clock_overlay.dm" +#include "code\modules\antagonists\clockcult\clock_effects\clock_sigils.dm" +#include "code\modules\antagonists\clockcult\clock_effects\general_markers.dm" +#include "code\modules\antagonists\clockcult\clock_effects\servant_blocker.dm" +#include "code\modules\antagonists\clockcult\clock_effects\spatial_gateway.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\clock_powerdrain.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\component_helpers.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\fabrication_helpers.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\hierophant_network.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\power_helpers.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\ratvarian_language.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\scripture_checks.dm" +#include "code\modules\antagonists\clockcult\clock_helpers\slab_abilities.dm" +#include "code\modules\antagonists\clockcult\clock_items\clock_components.dm" +#include "code\modules\antagonists\clockcult\clock_items\clockwork_armor.dm" +#include "code\modules\antagonists\clockcult\clock_items\clockwork_slab.dm" +#include "code\modules\antagonists\clockcult\clock_items\clockwork_weaponry.dm" +#include "code\modules\antagonists\clockcult\clock_items\construct_chassis.dm" +#include "code\modules\antagonists\clockcult\clock_items\integration_cog.dm" +#include "code\modules\antagonists\clockcult\clock_items\judicial_visor.dm" +#include "code\modules\antagonists\clockcult\clock_items\replica_fabricator.dm" +#include "code\modules\antagonists\clockcult\clock_items\soul_vessel.dm" +#include "code\modules\antagonists\clockcult\clock_items\wraith_spectacles.dm" +#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\_call_weapon.dm" +#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\ratvarian_spear.dm" +#include "code\modules\antagonists\clockcult\clock_mobs\_eminence.dm" +#include "code\modules\antagonists\clockcult\clock_mobs\clockwork_marauder.dm" +#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_applications.dm" +#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_cyborg.dm" +#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_drivers.dm" +#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_scripts.dm" +#include "code\modules\antagonists\clockcult\clock_structures\_trap_object.dm" +#include "code\modules\antagonists\clockcult\clock_structures\ark_of_the_clockwork_justicar.dm" +#include "code\modules\antagonists\clockcult\clock_structures\clockwork_obelisk.dm" +#include "code\modules\antagonists\clockcult\clock_structures\eminence_spire.dm" +#include "code\modules\antagonists\clockcult\clock_structures\heralds_beacon.dm" +#include "code\modules\antagonists\clockcult\clock_structures\mania_motor.dm" +#include "code\modules\antagonists\clockcult\clock_structures\ocular_warden.dm" +#include "code\modules\antagonists\clockcult\clock_structures\ratvar_the_clockwork_justicar.dm" +#include "code\modules\antagonists\clockcult\clock_structures\reflector.dm" +#include "code\modules\antagonists\clockcult\clock_structures\stargazer.dm" +#include "code\modules\antagonists\clockcult\clock_structures\taunting_trail.dm" +#include "code\modules\antagonists\clockcult\clock_structures\wall_gear.dm" +#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\lever.dm" +#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\pressure_sensor.dm" +#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\pressure_sensor_mech.dm" +#include "code\modules\antagonists\clockcult\clock_structures\trap_triggers\repeater.dm" +#include "code\modules\antagonists\clockcult\clock_structures\traps\brass_skewer.dm" +#include "code\modules\antagonists\clockcult\clock_structures\traps\power_null.dm" +#include "code\modules\antagonists\clockcult\clock_structures\traps\steam_vent.dm" +#include "code\modules\antagonists\cult\blood_magic.dm" +#include "code\modules\antagonists\cult\cult.dm" +#include "code\modules\antagonists\cult\cult_comms.dm" +#include "code\modules\antagonists\cult\cult_items.dm" +#include "code\modules\antagonists\cult\cult_structures.dm" +#include "code\modules\antagonists\cult\ritual.dm" +#include "code\modules\antagonists\cult\rune_spawn_action.dm" +#include "code\modules\antagonists\cult\runes.dm" +#include "code\modules\antagonists\devil\devil.dm" +#include "code\modules\antagonists\devil\devil_helpers.dm" +#include "code\modules\antagonists\devil\imp\imp.dm" +#include "code\modules\antagonists\devil\sintouched\objectives.dm" +#include "code\modules\antagonists\devil\sintouched\sintouched.dm" +#include "code\modules\antagonists\devil\true_devil\_true_devil.dm" +#include "code\modules\antagonists\devil\true_devil\inventory.dm" +#include "code\modules\antagonists\disease\disease_abilities.dm" +#include "code\modules\antagonists\disease\disease_datum.dm" +#include "code\modules\antagonists\disease\disease_disease.dm" +#include "code\modules\antagonists\disease\disease_event.dm" +#include "code\modules\antagonists\disease\disease_mob.dm" +#include "code\modules\antagonists\ert\ert.dm" +#include "code\modules\antagonists\greentext\greentext.dm" +#include "code\modules\antagonists\greybois\greybois.dm" +#include "code\modules\antagonists\highlander\highlander.dm" +#include "code\modules\antagonists\monkey\monkey.dm" +#include "code\modules\antagonists\morph\morph.dm" +#include "code\modules\antagonists\morph\morph_antag.dm" +#include "code\modules\antagonists\nightmare\nightmare.dm" +#include "code\modules\antagonists\ninja\ninja.dm" +#include "code\modules\antagonists\nukeop\clownop.dm" +#include "code\modules\antagonists\nukeop\nukeop.dm" +#include "code\modules\antagonists\nukeop\equipment\nuclear_challenge.dm" +#include "code\modules\antagonists\nukeop\equipment\nuclearbomb.dm" +#include "code\modules\antagonists\nukeop\equipment\pinpointer.dm" +#include "code\modules\antagonists\official\official.dm" +#include "code\modules\antagonists\overthrow\overthrow.dm" +#include "code\modules\antagonists\overthrow\overthrow_converter.dm" +#include "code\modules\antagonists\overthrow\overthrow_team.dm" +#include "code\modules\antagonists\pirate\pirate.dm" +#include "code\modules\antagonists\revenant\revenant.dm" +#include "code\modules\antagonists\revenant\revenant_abilities.dm" +#include "code\modules\antagonists\revenant\revenant_antag.dm" +#include "code\modules\antagonists\revenant\revenant_blight.dm" +#include "code\modules\antagonists\revenant\revenant_spawn_event.dm" +#include "code\modules\antagonists\revolution\revolution.dm" +#include "code\modules\antagonists\separatist\separatist.dm" +#include "code\modules\antagonists\slaughter\slaughter.dm" +#include "code\modules\antagonists\slaughter\slaughter_antag.dm" +#include "code\modules\antagonists\slaughter\slaughterevent.dm" +#include "code\modules\antagonists\survivalist\survivalist.dm" +#include "code\modules\antagonists\swarmer\swarmer.dm" +#include "code\modules\antagonists\swarmer\swarmer_event.dm" +#include "code\modules\antagonists\traitor\datum_traitor.dm" +#include "code\modules\antagonists\traitor\equipment\Malf_Modules.dm" +#include "code\modules\antagonists\traitor\IAA\internal_affairs.dm" +#include "code\modules\antagonists\valentines\heartbreaker.dm" +#include "code\modules\antagonists\valentines\valentine.dm" +#include "code\modules\antagonists\wishgranter\wishgranter.dm" +#include "code\modules\antagonists\wizard\wizard.dm" +#include "code\modules\antagonists\wizard\equipment\artefact.dm" +#include "code\modules\antagonists\wizard\equipment\soulstone.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook.dm" +#include "code\modules\antagonists\xeno\xeno.dm" +#include "code\modules\assembly\assembly.dm" +#include "code\modules\assembly\bomb.dm" +#include "code\modules\assembly\doorcontrol.dm" +#include "code\modules\assembly\flash.dm" +#include "code\modules\assembly\health.dm" +#include "code\modules\assembly\helpers.dm" +#include "code\modules\assembly\holder.dm" +#include "code\modules\assembly\igniter.dm" +#include "code\modules\assembly\infrared.dm" +#include "code\modules\assembly\mousetrap.dm" +#include "code\modules\assembly\proximity.dm" +#include "code\modules\assembly\shock_kit.dm" +#include "code\modules\assembly\signaler.dm" +#include "code\modules\assembly\timer.dm" +#include "code\modules\assembly\voice.dm" +#include "code\modules\atmospherics\multiz.dm" +#include "code\modules\atmospherics\environmental\LINDA_fire.dm" +#include "code\modules\atmospherics\environmental\LINDA_system.dm" +#include "code\modules\atmospherics\environmental\LINDA_turf_tile.dm" +#include "code\modules\atmospherics\gasmixtures\gas_mixture.dm" +#include "code\modules\atmospherics\gasmixtures\gas_types.dm" +#include "code\modules\atmospherics\gasmixtures\immutable_mixtures.dm" +#include "code\modules\atmospherics\gasmixtures\reactions.dm" +#include "code\modules\atmospherics\machinery\airalarm.dm" +#include "code\modules\atmospherics\machinery\atmosmachinery.dm" +#include "code\modules\atmospherics\machinery\datum_pipeline.dm" +#include "code\modules\atmospherics\machinery\components\components_base.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\binary_devices.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\circulator.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\dp_vent_pump.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\passive_gate.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\pump.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\relief_valve.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\valve.dm" +#include "code\modules\atmospherics\machinery\components\binary_devices\volume_pump.dm" +#include "code\modules\atmospherics\machinery\components\trinary_devices\filter.dm" +#include "code\modules\atmospherics\machinery\components\trinary_devices\mixer.dm" +#include "code\modules\atmospherics\machinery\components\trinary_devices\trinary_devices.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\cryo.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\heat_exchanger.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\outlet_injector.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\passive_vent.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\portables_connector.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\relief_valve.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\tank.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\thermomachine.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\unary_devices.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\vent_pump.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\vent_scrubber.dm" +#include "code\modules\atmospherics\machinery\other\meter.dm" +#include "code\modules\atmospherics\machinery\other\miner.dm" +#include "code\modules\atmospherics\machinery\pipes\layermanifold.dm" +#include "code\modules\atmospherics\machinery\pipes\manifold.dm" +#include "code\modules\atmospherics\machinery\pipes\manifold4w.dm" +#include "code\modules\atmospherics\machinery\pipes\mapping.dm" +#include "code\modules\atmospherics\machinery\pipes\pipes.dm" +#include "code\modules\atmospherics\machinery\pipes\simple.dm" +#include "code\modules\atmospherics\machinery\pipes\heat_exchange\he_pipes.dm" +#include "code\modules\atmospherics\machinery\pipes\heat_exchange\junction.dm" +#include "code\modules\atmospherics\machinery\pipes\heat_exchange\manifold.dm" +#include "code\modules\atmospherics\machinery\pipes\heat_exchange\manifold4w.dm" +#include "code\modules\atmospherics\machinery\pipes\heat_exchange\simple.dm" +#include "code\modules\atmospherics\machinery\portable\canister.dm" +#include "code\modules\atmospherics\machinery\portable\portable_atmospherics.dm" +#include "code\modules\atmospherics\machinery\portable\pump.dm" +#include "code\modules\atmospherics\machinery\portable\scrubber.dm" +#include "code\modules\awaymissions\away_props.dm" +#include "code\modules\awaymissions\bluespaceartillery.dm" +#include "code\modules\awaymissions\capture_the_flag.dm" +#include "code\modules\awaymissions\corpse.dm" +#include "code\modules\awaymissions\exile.dm" +#include "code\modules\awaymissions\gateway.dm" +#include "code\modules\awaymissions\pamphlet.dm" +#include "code\modules\awaymissions\signpost.dm" +#include "code\modules\awaymissions\super_secret_room.dm" +#include "code\modules\awaymissions\zlevel.dm" +#include "code\modules\awaymissions\mission_code\Academy.dm" +#include "code\modules\awaymissions\mission_code\Cabin.dm" +#include "code\modules\awaymissions\mission_code\caves.dm" +#include "code\modules\awaymissions\mission_code\centcomAway.dm" +#include "code\modules\awaymissions\mission_code\challenge.dm" +#include "code\modules\awaymissions\mission_code\moonoutpost19.dm" +#include "code\modules\awaymissions\mission_code\murderdome.dm" +#include "code\modules\awaymissions\mission_code\research.dm" +#include "code\modules\awaymissions\mission_code\snowdin.dm" +#include "code\modules\awaymissions\mission_code\spacebattle.dm" +#include "code\modules\awaymissions\mission_code\stationCollision.dm" +#include "code\modules\awaymissions\mission_code\undergroundoutpost45.dm" +#include "code\modules\awaymissions\mission_code\wildwest.dm" +#include "code\modules\bsql\includes.dm" +#include "code\modules\buildmode\bm_mode.dm" +#include "code\modules\buildmode\buildmode.dm" +#include "code\modules\buildmode\buttons.dm" +#include "code\modules\buildmode\effects\line.dm" +#include "code\modules\buildmode\submodes\advanced.dm" +#include "code\modules\buildmode\submodes\area_edit.dm" +#include "code\modules\buildmode\submodes\basic.dm" +#include "code\modules\buildmode\submodes\boom.dm" +#include "code\modules\buildmode\submodes\copy.dm" +#include "code\modules\buildmode\submodes\fill.dm" +#include "code\modules\buildmode\submodes\mapgen.dm" +#include "code\modules\buildmode\submodes\throwing.dm" +#include "code\modules\buildmode\submodes\variable_edit.dm" +#include "code\modules\cargo\bounty.dm" +#include "code\modules\cargo\bounty_console.dm" +#include "code\modules\cargo\centcom_podlauncher.dm" +#include "code\modules\cargo\console.dm" +#include "code\modules\cargo\export_scanner.dm" +#include "code\modules\cargo\exports.dm" +#include "code\modules\cargo\expressconsole.dm" +#include "code\modules\cargo\gondolapod.dm" +#include "code\modules\cargo\order.dm" +#include "code\modules\cargo\packs.dm" +#include "code\modules\cargo\supplypod.dm" +#include "code\modules\cargo\supplypod_beacon.dm" +#include "code\modules\cargo\bounties\assistant.dm" +#include "code\modules\cargo\bounties\botany.dm" +#include "code\modules\cargo\bounties\chef.dm" +#include "code\modules\cargo\bounties\engineering.dm" +#include "code\modules\cargo\bounties\item.dm" +#include "code\modules\cargo\bounties\mech.dm" +#include "code\modules\cargo\bounties\medical.dm" +#include "code\modules\cargo\bounties\mining.dm" +#include "code\modules\cargo\bounties\reagent.dm" +#include "code\modules\cargo\bounties\science.dm" +#include "code\modules\cargo\bounties\security.dm" +#include "code\modules\cargo\bounties\slime.dm" +#include "code\modules\cargo\bounties\special.dm" +#include "code\modules\cargo\bounties\virus.dm" +#include "code\modules\cargo\exports\food_wine.dm" +#include "code\modules\cargo\exports\gear.dm" +#include "code\modules\cargo\exports\large_objects.dm" +#include "code\modules\cargo\exports\manifest.dm" +#include "code\modules\cargo\exports\materials.dm" +#include "code\modules\cargo\exports\organs_robotics.dm" +#include "code\modules\cargo\exports\parts.dm" +#include "code\modules\cargo\exports\seeds.dm" +#include "code\modules\cargo\exports\sheets.dm" +#include "code\modules\cargo\exports\tools.dm" +#include "code\modules\cargo\exports\weapons.dm" +#include "code\modules\cargo\packs\armory.dm" +#include "code\modules\cargo\packs\costumes_toys.dm" +#include "code\modules\cargo\packs\emergency.dm" +#include "code\modules\cargo\packs\engine.dm" +#include "code\modules\cargo\packs\engineering.dm" +#include "code\modules\cargo\packs\livestock.dm" +#include "code\modules\cargo\packs\materials.dm" +#include "code\modules\cargo\packs\medical.dm" +#include "code\modules\cargo\packs\misc.dm" +#include "code\modules\cargo\packs\organic.dm" +#include "code\modules\cargo\packs\science.dm" +#include "code\modules\cargo\packs\security.dm" +#include "code\modules\cargo\packs\service.dm" +#include "code\modules\chatter\chatter.dm" +#include "code\modules\client\asset_cache.dm" +#include "code\modules\client\client_colour.dm" +#include "code\modules\client\client_defines.dm" +#include "code\modules\client\client_procs.dm" +#include "code\modules\client\darkmode.dm" +#include "code\modules\client\message.dm" +#include "code\modules\client\player_details.dm" +#include "code\modules\client\preferences.dm" +#include "code\modules\client\preferences_savefile.dm" +#include "code\modules\client\preferences_toggles.dm" +#include "code\modules\client\preferences_vr.dm" +#include "code\modules\client\verbs\aooc.dm" +#include "code\modules\client\verbs\etips.dm" +#include "code\modules\client\verbs\looc.dm" +#include "code\modules\client\verbs\ooc.dm" +#include "code\modules\client\verbs\ping.dm" +#include "code\modules\client\verbs\suicide.dm" +#include "code\modules\client\verbs\who.dm" +#include "code\modules\clothing\chameleon.dm" +#include "code\modules\clothing\clothing.dm" +#include "code\modules\clothing\ears\_ears.dm" +#include "code\modules\clothing\glasses\_glasses.dm" +#include "code\modules\clothing\glasses\engine_goggles.dm" +#include "code\modules\clothing\glasses\hud.dm" +#include "code\modules\clothing\glasses\vg_glasses.dm" +#include "code\modules\clothing\gloves\_gloves.dm" +#include "code\modules\clothing\gloves\boxing.dm" +#include "code\modules\clothing\gloves\color.dm" +#include "code\modules\clothing\gloves\miscellaneous.dm" +#include "code\modules\clothing\gloves\ring.dm" +#include "code\modules\clothing\gloves\vg_gloves.dm" +#include "code\modules\clothing\head\_head.dm" +#include "code\modules\clothing\head\beanie.dm" +#include "code\modules\clothing\head\cit_hats.dm" +#include "code\modules\clothing\head\collectable.dm" +#include "code\modules\clothing\head\hardhat.dm" +#include "code\modules\clothing\head\helmet.dm" +#include "code\modules\clothing\head\jobs.dm" +#include "code\modules\clothing\head\misc.dm" +#include "code\modules\clothing\head\misc_special.dm" +#include "code\modules\clothing\head\soft_caps.dm" +#include "code\modules\clothing\head\vg_hats.dm" +#include "code\modules\clothing\masks\_masks.dm" +#include "code\modules\clothing\masks\boxing.dm" +#include "code\modules\clothing\masks\breath.dm" +#include "code\modules\clothing\masks\gasmask.dm" +#include "code\modules\clothing\masks\hailer.dm" +#include "code\modules\clothing\masks\miscellaneous.dm" +#include "code\modules\clothing\masks\vg_masks.dm" +#include "code\modules\clothing\neck\_neck.dm" +#include "code\modules\clothing\outfits\ert.dm" +#include "code\modules\clothing\outfits\event.dm" +#include "code\modules\clothing\outfits\plasmaman.dm" +#include "code\modules\clothing\outfits\standard.dm" +#include "code\modules\clothing\outfits\vr.dm" +#include "code\modules\clothing\outfits\vv_outfit.dm" +#include "code\modules\clothing\shoes\_shoes.dm" +#include "code\modules\clothing\shoes\bananashoes.dm" +#include "code\modules\clothing\shoes\colour.dm" +#include "code\modules\clothing\shoes\magboots.dm" +#include "code\modules\clothing\shoes\miscellaneous.dm" +#include "code\modules\clothing\shoes\taeclowndo.dm" +#include "code\modules\clothing\shoes\vg_shoes.dm" +#include "code\modules\clothing\spacesuits\_spacesuits.dm" +#include "code\modules\clothing\spacesuits\chronosuit.dm" +#include "code\modules\clothing\spacesuits\hardsuit.dm" +#include "code\modules\clothing\spacesuits\miscellaneous.dm" +#include "code\modules\clothing\spacesuits\plasmamen.dm" +#include "code\modules\clothing\spacesuits\syndi.dm" +#include "code\modules\clothing\spacesuits\vg_spess.dm" +#include "code\modules\clothing\suits\_suits.dm" +#include "code\modules\clothing\suits\armor.dm" +#include "code\modules\clothing\suits\bio.dm" +#include "code\modules\clothing\suits\cloaks.dm" +#include "code\modules\clothing\suits\jobs.dm" +#include "code\modules\clothing\suits\labcoat.dm" +#include "code\modules\clothing\suits\miscellaneous.dm" +#include "code\modules\clothing\suits\reactive_armour.dm" +#include "code\modules\clothing\suits\toggles.dm" +#include "code\modules\clothing\suits\utility.dm" +#include "code\modules\clothing\suits\vg_suits.dm" +#include "code\modules\clothing\suits\wiz_robe.dm" +#include "code\modules\clothing\under\_under.dm" +#include "code\modules\clothing\under\accessories.dm" +#include "code\modules\clothing\under\color.dm" +#include "code\modules\clothing\under\miscellaneous.dm" +#include "code\modules\clothing\under\pants.dm" +#include "code\modules\clothing\under\shorts.dm" +#include "code\modules\clothing\under\syndicate.dm" +#include "code\modules\clothing\under\trek.dm" +#include "code\modules\clothing\under\vg_under.dm" +#include "code\modules\clothing\under\jobs\civilian.dm" +#include "code\modules\clothing\under\jobs\engineering.dm" +#include "code\modules\clothing\under\jobs\medsci.dm" +#include "code\modules\clothing\under\jobs\security.dm" +#include "code\modules\clothing\under\jobs\Plasmaman\civilian_service.dm" +#include "code\modules\clothing\under\jobs\Plasmaman\engineering.dm" +#include "code\modules\clothing\under\jobs\Plasmaman\medsci.dm" +#include "code\modules\clothing\under\jobs\Plasmaman\security.dm" +#include "code\modules\crafting\craft.dm" +#include "code\modules\crafting\guncrafting.dm" +#include "code\modules\crafting\recipes.dm" +#include "code\modules\crafting\recipes\recipes_clothing.dm" +#include "code\modules\crafting\recipes\recipes_misc.dm" +#include "code\modules\crafting\recipes\recipes_primal.dm" +#include "code\modules\crafting\recipes\recipes_robot.dm" +#include "code\modules\crafting\recipes\recipes_weapon_and_ammo.dm" +#include "code\modules\detectivework\detective_work.dm" +#include "code\modules\detectivework\evidence.dm" +#include "code\modules\detectivework\scanner.dm" +#include "code\modules\emoji\emoji_parse.dm" +#include "code\modules\error_handler\error_handler.dm" +#include "code\modules\error_handler\error_viewer.dm" +#include "code\modules\events\_event.dm" +#include "code\modules\events\abductor.dm" +#include "code\modules\events\alien_infestation.dm" +#include "code\modules\events\anomaly.dm" +#include "code\modules\events\anomaly_bluespace.dm" +#include "code\modules\events\anomaly_flux.dm" +#include "code\modules\events\anomaly_grav.dm" +#include "code\modules\events\anomaly_pyro.dm" +#include "code\modules\events\anomaly_vortex.dm" +#include "code\modules\events\aurora_aquilae.dm" +#include "code\modules\events\aurora_caelus.dm" +#include "code\modules\events\blob.dm" +#include "code\modules\events\brand_intelligence.dm" +#include "code\modules\events\bureaucratic_error.dm" +#include "code\modules\events\camerafailure.dm" +#include "code\modules\events\carp_migration.dm" +#include "code\modules\events\carpteam.dm" +#include "code\modules\events\communications_blackout.dm" +#include "code\modules\events\devil.dm" +#include "code\modules\events\disease_outbreak.dm" +#include "code\modules\events\dust.dm" +#include "code\modules\events\electrical_storm.dm" +#include "code\modules\events\false_alarm.dm" +#include "code\modules\events\floorcluwne.dm" +#include "code\modules\events\ghost_role.dm" +#include "code\modules\events\grid_check.dm" +#include "code\modules\events\heart_attack.dm" +#include "code\modules\events\high_priority_bounty.dm" +#include "code\modules\events\immovable_rod.dm" +#include "code\modules\events\ion_storm.dm" +#include "code\modules\events\major_dust.dm" +#include "code\modules\events\mass_hallucination.dm" +#include "code\modules\events\meateor_wave.dm" +#include "code\modules\events\meteor_wave.dm" +#include "code\modules\events\mice_migration.dm" +#include "code\modules\events\nightmare.dm" +#include "code\modules\events\operative.dm" +#include "code\modules\events\pirates.dm" +#include "code\modules\events\portal_storm.dm" +#include "code\modules\events\prison_break.dm" +#include "code\modules\events\processor_overload.dm" +#include "code\modules\events\radiation_storm.dm" +#include "code\modules\events\sentience.dm" +#include "code\modules\events\shuttle_loan.dm" +#include "code\modules\events\spacevine.dm" +#include "code\modules\events\spider_infestation.dm" +#include "code\modules\events\spontaneous_appendicitis.dm" +#include "code\modules\events\vent_clog.dm" +#include "code\modules\events\wormholes.dm" +#include "code\modules\events\holiday\halloween.dm" +#include "code\modules\events\holiday\vday.dm" +#include "code\modules\events\holiday\xmas.dm" +#include "code\modules\events\wizard\aid.dm" +#include "code\modules\events\wizard\blobies.dm" +#include "code\modules\events\wizard\curseditems.dm" +#include "code\modules\events\wizard\departmentrevolt.dm" +#include "code\modules\events\wizard\fakeexplosion.dm" +#include "code\modules\events\wizard\ghost.dm" +#include "code\modules\events\wizard\greentext.dm" +#include "code\modules\events\wizard\imposter.dm" +#include "code\modules\events\wizard\invincible.dm" +#include "code\modules\events\wizard\lava.dm" +#include "code\modules\events\wizard\magicarp.dm" +#include "code\modules\events\wizard\petsplosion.dm" +#include "code\modules\events\wizard\race.dm" +#include "code\modules\events\wizard\rpgloot.dm" +#include "code\modules\events\wizard\shuffle.dm" +#include "code\modules\events\wizard\summons.dm" +#include "code\modules\fields\fields.dm" +#include "code\modules\fields\gravity.dm" +#include "code\modules\fields\peaceborg_dampener.dm" +#include "code\modules\fields\timestop.dm" +#include "code\modules\fields\turf_objects.dm" +#include "code\modules\flufftext\Dreaming.dm" +#include "code\modules\flufftext\Hallucination.dm" +#include "code\modules\food_and_drinks\autobottler.dm" +#include "code\modules\food_and_drinks\food.dm" +#include "code\modules\food_and_drinks\pizzabox.dm" +#include "code\modules\food_and_drinks\drinks\drinks.dm" +#include "code\modules\food_and_drinks\drinks\drinks\bottle.dm" +#include "code\modules\food_and_drinks\drinks\drinks\drinkingglass.dm" +#include "code\modules\food_and_drinks\food\condiment.dm" +#include "code\modules\food_and_drinks\food\customizables.dm" +#include "code\modules\food_and_drinks\food\snacks.dm" +#include "code\modules\food_and_drinks\food\snacks_bread.dm" +#include "code\modules\food_and_drinks\food\snacks_burgers.dm" +#include "code\modules\food_and_drinks\food\snacks_cake.dm" +#include "code\modules\food_and_drinks\food\snacks_egg.dm" +#include "code\modules\food_and_drinks\food\snacks_frozen.dm" +#include "code\modules\food_and_drinks\food\snacks_meat.dm" +#include "code\modules\food_and_drinks\food\snacks_other.dm" +#include "code\modules\food_and_drinks\food\snacks_pastry.dm" +#include "code\modules\food_and_drinks\food\snacks_pie.dm" +#include "code\modules\food_and_drinks\food\snacks_pizza.dm" +#include "code\modules\food_and_drinks\food\snacks_salad.dm" +#include "code\modules\food_and_drinks\food\snacks_sandwichtoast.dm" +#include "code\modules\food_and_drinks\food\snacks_soup.dm" +#include "code\modules\food_and_drinks\food\snacks_spaghetti.dm" +#include "code\modules\food_and_drinks\food\snacks_sushi.dm" +#include "code\modules\food_and_drinks\food\snacks_vend.dm" +#include "code\modules\food_and_drinks\food\snacks\dough.dm" +#include "code\modules\food_and_drinks\food\snacks\meat.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\deep_fryer.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\food_cart.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\gibber.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\grill.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\icecream_vat.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\microwave.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\monkeyrecycler.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\processor.dm" +#include "code\modules\food_and_drinks\kitchen_machinery\smartfridge.dm" +#include "code\modules\food_and_drinks\recipes\drinks_recipes.dm" +#include "code\modules\food_and_drinks\recipes\food_mixtures.dm" +#include "code\modules\food_and_drinks\recipes\processor_recipes.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_bread.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_burger.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_cake.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_egg.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_frozen.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_meat.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_misc.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_pastry.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_pie.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_pizza.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_salad.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_sandwich.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_soup.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_spaghetti.dm" +#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_sushi.dm" +#include "code\modules\games\cas.dm" +#include "code\modules\goonchat\browserOutput.dm" +#include "code\modules\goonchat\jsErrorHandler.dm" +#include "code\modules\holiday\easter.dm" +#include "code\modules\holiday\holidays.dm" +#include "code\modules\holiday\halloween\bartholomew.dm" +#include "code\modules\holiday\halloween\jacqueen.dm" +#include "code\modules\holodeck\area_copy.dm" +#include "code\modules\holodeck\computer.dm" +#include "code\modules\holodeck\holo_effect.dm" +#include "code\modules\holodeck\items.dm" +#include "code\modules\holodeck\mobs.dm" +#include "code\modules\holodeck\turfs.dm" +#include "code\modules\hydroponics\biogenerator.dm" +#include "code\modules\hydroponics\fermenting_barrel.dm" +#include "code\modules\hydroponics\gene_modder.dm" +#include "code\modules\hydroponics\grown.dm" +#include "code\modules\hydroponics\growninedible.dm" +#include "code\modules\hydroponics\hydroitemdefines.dm" +#include "code\modules\hydroponics\hydroponics.dm" +#include "code\modules\hydroponics\plant_genes.dm" +#include "code\modules\hydroponics\sample.dm" +#include "code\modules\hydroponics\seed_extractor.dm" +#include "code\modules\hydroponics\seeds.dm" +#include "code\modules\hydroponics\beekeeping\beebox.dm" +#include "code\modules\hydroponics\beekeeping\beekeeper_suit.dm" +#include "code\modules\hydroponics\beekeeping\honey_frame.dm" +#include "code\modules\hydroponics\beekeeping\honeycomb.dm" +#include "code\modules\hydroponics\grown\ambrosia.dm" +#include "code\modules\hydroponics\grown\apple.dm" +#include "code\modules\hydroponics\grown\banana.dm" +#include "code\modules\hydroponics\grown\beans.dm" +#include "code\modules\hydroponics\grown\berries.dm" +#include "code\modules\hydroponics\grown\cannabis.dm" +#include "code\modules\hydroponics\grown\cereals.dm" +#include "code\modules\hydroponics\grown\chili.dm" +#include "code\modules\hydroponics\grown\citrus.dm" +#include "code\modules\hydroponics\grown\cocoa_vanilla.dm" +#include "code\modules\hydroponics\grown\corn.dm" +#include "code\modules\hydroponics\grown\cotton.dm" +#include "code\modules\hydroponics\grown\eggplant.dm" +#include "code\modules\hydroponics\grown\flowers.dm" +#include "code\modules\hydroponics\grown\garlic.dm" +#include "code\modules\hydroponics\grown\grass_carpet.dm" +#include "code\modules\hydroponics\grown\kudzu.dm" +#include "code\modules\hydroponics\grown\melon.dm" +#include "code\modules\hydroponics\grown\misc.dm" +#include "code\modules\hydroponics\grown\mushrooms.dm" +#include "code\modules\hydroponics\grown\nettle.dm" +#include "code\modules\hydroponics\grown\onion.dm" +#include "code\modules\hydroponics\grown\peach.dm" +#include "code\modules\hydroponics\grown\peanuts.dm" +#include "code\modules\hydroponics\grown\pineapple.dm" +#include "code\modules\hydroponics\grown\potato.dm" +#include "code\modules\hydroponics\grown\pumpkin.dm" +#include "code\modules\hydroponics\grown\random.dm" +#include "code\modules\hydroponics\grown\replicapod.dm" +#include "code\modules\hydroponics\grown\root.dm" +#include "code\modules\hydroponics\grown\tea_coffee.dm" +#include "code\modules\hydroponics\grown\tobacco.dm" +#include "code\modules\hydroponics\grown\tomato.dm" +#include "code\modules\hydroponics\grown\towercap.dm" +#include "code\modules\integrated_electronics\_defines.dm" +#include "code\modules\integrated_electronics\core\analyzer.dm" +#include "code\modules\integrated_electronics\core\assemblies.dm" +#include "code\modules\integrated_electronics\core\debugger.dm" +#include "code\modules\integrated_electronics\core\detailer.dm" +#include "code\modules\integrated_electronics\core\helpers.dm" +#include "code\modules\integrated_electronics\core\integrated_circuit.dm" +#include "code\modules\integrated_electronics\core\pins.dm" +#include "code\modules\integrated_electronics\core\printer.dm" +#include "code\modules\integrated_electronics\core\saved_circuits.dm" +#include "code\modules\integrated_electronics\core\wirer.dm" +#include "code\modules\integrated_electronics\core\special_pins\boolean_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\char_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\color_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\dir_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\index_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\list_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\number_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\ref_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\selfref_pin.dm" +#include "code\modules\integrated_electronics\core\special_pins\string_pin.dm" +#include "code\modules\integrated_electronics\passive\passive.dm" +#include "code\modules\integrated_electronics\passive\power.dm" +#include "code\modules\integrated_electronics\subtypes\access.dm" +#include "code\modules\integrated_electronics\subtypes\arithmetic.dm" +#include "code\modules\integrated_electronics\subtypes\atmospherics.dm" +#include "code\modules\integrated_electronics\subtypes\converters.dm" +#include "code\modules\integrated_electronics\subtypes\data_transfer.dm" +#include "code\modules\integrated_electronics\subtypes\input.dm" +#include "code\modules\integrated_electronics\subtypes\lists.dm" +#include "code\modules\integrated_electronics\subtypes\logic.dm" +#include "code\modules\integrated_electronics\subtypes\manipulation.dm" +#include "code\modules\integrated_electronics\subtypes\memory.dm" +#include "code\modules\integrated_electronics\subtypes\output.dm" +#include "code\modules\integrated_electronics\subtypes\power.dm" +#include "code\modules\integrated_electronics\subtypes\reagents.dm" +#include "code\modules\integrated_electronics\subtypes\smart.dm" +#include "code\modules\integrated_electronics\subtypes\text.dm" +#include "code\modules\integrated_electronics\subtypes\time.dm" +#include "code\modules\integrated_electronics\subtypes\trig.dm" +#include "code\modules\integrated_electronics\subtypes\weaponized.dm" +#include "code\modules\jobs\access.dm" +#include "code\modules\jobs\job_exp.dm" +#include "code\modules\jobs\jobs.dm" +#include "code\modules\jobs\job_types\assistant.dm" +#include "code\modules\jobs\job_types\captain.dm" +#include "code\modules\jobs\job_types\cargo_service.dm" +#include "code\modules\jobs\job_types\civilian.dm" +#include "code\modules\jobs\job_types\civilian_chaplain.dm" +#include "code\modules\jobs\job_types\engineering.dm" +#include "code\modules\jobs\job_types\job.dm" +#include "code\modules\jobs\job_types\job_alt_titles.dm" +#include "code\modules\jobs\job_types\medical.dm" +#include "code\modules\jobs\job_types\science.dm" +#include "code\modules\jobs\job_types\security.dm" +#include "code\modules\jobs\job_types\silicon.dm" +#include "code\modules\jobs\map_changes\map_changes.dm" +#include "code\modules\keybindings\bindings_admin.dm" +#include "code\modules\keybindings\bindings_atom.dm" +#include "code\modules\keybindings\bindings_carbon.dm" +#include "code\modules\keybindings\bindings_client.dm" +#include "code\modules\keybindings\bindings_human.dm" +#include "code\modules\keybindings\bindings_living.dm" +#include "code\modules\keybindings\bindings_mob.dm" +#include "code\modules\keybindings\bindings_robot.dm" +#include "code\modules\keybindings\focus.dm" +#include "code\modules\keybindings\setup.dm" +#include "code\modules\language\aphasia.dm" +#include "code\modules\language\beachbum.dm" +#include "code\modules\language\codespeak.dm" +#include "code\modules\language\common.dm" +#include "code\modules\language\draconic.dm" +#include "code\modules\language\drone.dm" +#include "code\modules\language\language.dm" +#include "code\modules\language\language_holder.dm" +#include "code\modules\language\language_menu.dm" +#include "code\modules\language\machine.dm" +#include "code\modules\language\monkey.dm" +#include "code\modules\language\mushroom.dm" +#include "code\modules\language\narsian.dm" +#include "code\modules\language\ratvarian.dm" +#include "code\modules\language\slime.dm" +#include "code\modules\language\swarmer.dm" +#include "code\modules\language\vampiric.dm" +#include "code\modules\language\xenocommon.dm" +#include "code\modules\library\lib_codex_gigas.dm" +#include "code\modules\library\lib_items.dm" +#include "code\modules\library\lib_machines.dm" +#include "code\modules\library\random_books.dm" +#include "code\modules\library\soapstone.dm" +#include "code\modules\lighting\lighting_area.dm" +#include "code\modules\lighting\lighting_atom.dm" +#include "code\modules\lighting\lighting_corner.dm" +#include "code\modules\lighting\lighting_object.dm" +#include "code\modules\lighting\lighting_setup.dm" +#include "code\modules\lighting\lighting_source.dm" +#include "code\modules\lighting\lighting_turf.dm" +#include "code\modules\mapping\dmm_suite.dm" +#include "code\modules\mapping\map_template.dm" +#include "code\modules\mapping\preloader.dm" +#include "code\modules\mapping\reader.dm" +#include "code\modules\mapping\ruins.dm" +#include "code\modules\mapping\verify.dm" +#include "code\modules\mapping\mapping_helpers\_mapping_helpers.dm" +#include "code\modules\mapping\mapping_helpers\baseturf.dm" +#include "code\modules\mapping\mapping_helpers\network_builder\_network_builder.dm" +#include "code\modules\mapping\mapping_helpers\network_builder\atmos_pipe.dm" +#include "code\modules\mapping\mapping_helpers\network_builder\power_cables.dm" +#include "code\modules\mapping\space_management\multiz_helpers.dm" +#include "code\modules\mapping\space_management\space_level.dm" +#include "code\modules\mapping\space_management\space_reservation.dm" +#include "code\modules\mapping\space_management\space_transition.dm" +#include "code\modules\mapping\space_management\traits.dm" +#include "code\modules\mapping\space_management\zlevel_manager.dm" +#include "code\modules\mining\abandoned_crates.dm" +#include "code\modules\mining\aux_base.dm" +#include "code\modules\mining\aux_base_camera.dm" +#include "code\modules\mining\fulton.dm" +#include "code\modules\mining\machine_processing.dm" +#include "code\modules\mining\machine_redemption.dm" +#include "code\modules\mining\machine_silo.dm" +#include "code\modules\mining\machine_stacking.dm" +#include "code\modules\mining\machine_unloading.dm" +#include "code\modules\mining\machine_vending.dm" +#include "code\modules\mining\mine_items.dm" +#include "code\modules\mining\minebot.dm" +#include "code\modules\mining\mint.dm" +#include "code\modules\mining\money_bag.dm" +#include "code\modules\mining\ores_coins.dm" +#include "code\modules\mining\satchel_ore_boxdm.dm" +#include "code\modules\mining\shelters.dm" +#include "code\modules\mining\equipment\explorer_gear.dm" +#include "code\modules\mining\equipment\goliath_hide.dm" +#include "code\modules\mining\equipment\kinetic_crusher.dm" +#include "code\modules\mining\equipment\lazarus_injector.dm" +#include "code\modules\mining\equipment\marker_beacons.dm" +#include "code\modules\mining\equipment\mineral_scanner.dm" +#include "code\modules\mining\equipment\mining_tools.dm" +#include "code\modules\mining\equipment\regenerative_core.dm" +#include "code\modules\mining\equipment\resonator.dm" +#include "code\modules\mining\equipment\survival_pod.dm" +#include "code\modules\mining\equipment\vendor_items.dm" +#include "code\modules\mining\equipment\wormhole_jaunter.dm" +#include "code\modules\mining\laborcamp\laborshuttle.dm" +#include "code\modules\mining\laborcamp\laborstacker.dm" +#include "code\modules\mining\lavaland\ash_flora.dm" +#include "code\modules\mining\lavaland\necropolis_chests.dm" +#include "code\modules\mining\lavaland\ruins\gym.dm" +#include "code\modules\mob\death.dm" +#include "code\modules\mob\emote.dm" +#include "code\modules\mob\inventory.dm" +#include "code\modules\mob\login.dm" +#include "code\modules\mob\logout.dm" +#include "code\modules\mob\mob.dm" +#include "code\modules\mob\mob_defines.dm" +#include "code\modules\mob\mob_helpers.dm" +#include "code\modules\mob\mob_movement.dm" +#include "code\modules\mob\mob_movespeed.dm" +#include "code\modules\mob\mob_transformation_simple.dm" +#include "code\modules\mob\say.dm" +#include "code\modules\mob\say_vr.dm" +#include "code\modules\mob\status_procs.dm" +#include "code\modules\mob\transform_procs.dm" +#include "code\modules\mob\typing_indicator.dm" +#include "code\modules\mob\update_icons.dm" +#include "code\modules\mob\camera\camera.dm" +#include "code\modules\mob\dead\dead.dm" +#include "code\modules\mob\dead\new_player\login.dm" +#include "code\modules\mob\dead\new_player\logout.dm" +#include "code\modules\mob\dead\new_player\new_player.dm" +#include "code\modules\mob\dead\new_player\poll.dm" +#include "code\modules\mob\dead\new_player\preferences_setup.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\_sprite_accessories.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\body_markings.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\caps.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\ears.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\frills.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\hair_face.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\hair_head.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\horns.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\legs.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\moth_fluff.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\moth_wings.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\pines.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\snouts.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\socks.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\tails.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\undershirt.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\underwear.dm" +#include "code\modules\mob\dead\new_player\sprite_accessories\wings.dm" +#include "code\modules\mob\dead\observer\login.dm" +#include "code\modules\mob\dead\observer\logout.dm" +#include "code\modules\mob\dead\observer\notificationprefs.dm" +#include "code\modules\mob\dead\observer\observer.dm" +#include "code\modules\mob\dead\observer\observer_movement.dm" +#include "code\modules\mob\dead\observer\say.dm" +#include "code\modules\mob\living\blood.dm" +#include "code\modules\mob\living\bloodcrawl.dm" +#include "code\modules\mob\living\damage_procs.dm" +#include "code\modules\mob\living\death.dm" +#include "code\modules\mob\living\emote.dm" +#include "code\modules\mob\living\life.dm" +#include "code\modules\mob\living\living.dm" +#include "code\modules\mob\living\living_defense.dm" +#include "code\modules\mob\living\living_defines.dm" +#include "code\modules\mob\living\living_movement.dm" +#include "code\modules\mob\living\login.dm" +#include "code\modules\mob\living\logout.dm" +#include "code\modules\mob\living\say.dm" +#include "code\modules\mob\living\status_procs.dm" +#include "code\modules\mob\living\taste.dm" +#include "code\modules\mob\living\ventcrawling.dm" +#include "code\modules\mob\living\brain\brain.dm" +#include "code\modules\mob\living\brain\brain_item.dm" +#include "code\modules\mob\living\brain\death.dm" +#include "code\modules\mob\living\brain\emote.dm" +#include "code\modules\mob\living\brain\life.dm" +#include "code\modules\mob\living\brain\MMI.dm" +#include "code\modules\mob\living\brain\posibrain.dm" +#include "code\modules\mob\living\brain\say.dm" +#include "code\modules\mob\living\brain\status_procs.dm" +#include "code\modules\mob\living\carbon\carbon.dm" +#include "code\modules\mob\living\carbon\carbon_defense.dm" +#include "code\modules\mob\living\carbon\carbon_defines.dm" +#include "code\modules\mob\living\carbon\carbon_movement.dm" +#include "code\modules\mob\living\carbon\damage_procs.dm" +#include "code\modules\mob\living\carbon\death.dm" +#include "code\modules\mob\living\carbon\emote.dm" +#include "code\modules\mob\living\carbon\examine.dm" +#include "code\modules\mob\living\carbon\give.dm" +#include "code\modules\mob\living\carbon\inventory.dm" +#include "code\modules\mob\living\carbon\life.dm" +#include "code\modules\mob\living\carbon\say.dm" +#include "code\modules\mob\living\carbon\status_procs.dm" +#include "code\modules\mob\living\carbon\update_icons.dm" +#include "code\modules\mob\living\carbon\alien\alien.dm" +#include "code\modules\mob\living\carbon\alien\alien_defense.dm" +#include "code\modules\mob\living\carbon\alien\damage_procs.dm" +#include "code\modules\mob\living\carbon\alien\death.dm" +#include "code\modules\mob\living\carbon\alien\emote.dm" +#include "code\modules\mob\living\carbon\alien\life.dm" +#include "code\modules\mob\living\carbon\alien\login.dm" +#include "code\modules\mob\living\carbon\alien\logout.dm" +#include "code\modules\mob\living\carbon\alien\organs.dm" +#include "code\modules\mob\living\carbon\alien\say.dm" +#include "code\modules\mob\living\carbon\alien\screen.dm" +#include "code\modules\mob\living\carbon\alien\status_procs.dm" +#include "code\modules\mob\living\carbon\alien\update_icons.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\alien_powers.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\death.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\humanoid.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\humanoid_defense.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\inventory.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\life.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\queen.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\update_icons.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\caste\drone.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\caste\hunter.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\caste\praetorian.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\caste\sentinel.dm" +#include "code\modules\mob\living\carbon\alien\larva\death.dm" +#include "code\modules\mob\living\carbon\alien\larva\inventory.dm" +#include "code\modules\mob\living\carbon\alien\larva\larva.dm" +#include "code\modules\mob\living\carbon\alien\larva\larva_defense.dm" +#include "code\modules\mob\living\carbon\alien\larva\life.dm" +#include "code\modules\mob\living\carbon\alien\larva\powers.dm" +#include "code\modules\mob\living\carbon\alien\larva\update_icons.dm" +#include "code\modules\mob\living\carbon\alien\special\alien_embryo.dm" +#include "code\modules\mob\living\carbon\alien\special\facehugger.dm" +#include "code\modules\mob\living\carbon\human\damage_procs.dm" +#include "code\modules\mob\living\carbon\human\death.dm" +#include "code\modules\mob\living\carbon\human\dummy.dm" +#include "code\modules\mob\living\carbon\human\emote.dm" +#include "code\modules\mob\living\carbon\human\examine.dm" +#include "code\modules\mob\living\carbon\human\examine_vr.dm" +#include "code\modules\mob\living\carbon\human\human.dm" +#include "code\modules\mob\living\carbon\human\human_defense.dm" +#include "code\modules\mob\living\carbon\human\human_defines.dm" +#include "code\modules\mob\living\carbon\human\human_helpers.dm" +#include "code\modules\mob\living\carbon\human\human_movement.dm" +#include "code\modules\mob\living\carbon\human\inventory.dm" +#include "code\modules\mob\living\carbon\human\life.dm" +#include "code\modules\mob\living\carbon\human\physiology.dm" +#include "code\modules\mob\living\carbon\human\say.dm" +#include "code\modules\mob\living\carbon\human\species.dm" +#include "code\modules\mob\living\carbon\human\status_procs.dm" +#include "code\modules\mob\living\carbon\human\typing_indicator.dm" +#include "code\modules\mob\living\carbon\human\update_icons.dm" +#include "code\modules\mob\living\carbon\human\species_types\abductors.dm" +#include "code\modules\mob\living\carbon\human\species_types\android.dm" +#include "code\modules\mob\living\carbon\human\species_types\angel.dm" +#include "code\modules\mob\living\carbon\human\species_types\corporate.dm" +#include "code\modules\mob\living\carbon\human\species_types\dullahan.dm" +#include "code\modules\mob\living\carbon\human\species_types\felinid.dm" +#include "code\modules\mob\living\carbon\human\species_types\flypeople.dm" +#include "code\modules\mob\living\carbon\human\species_types\golems.dm" +#include "code\modules\mob\living\carbon\human\species_types\humans.dm" +#include "code\modules\mob\living\carbon\human\species_types\jellypeople.dm" +#include "code\modules\mob\living\carbon\human\species_types\lizardpeople.dm" +#include "code\modules\mob\living\carbon\human\species_types\mothmen.dm" +#include "code\modules\mob\living\carbon\human\species_types\mushpeople.dm" +#include "code\modules\mob\living\carbon\human\species_types\plasmamen.dm" +#include "code\modules\mob\living\carbon\human\species_types\podpeople.dm" +#include "code\modules\mob\living\carbon\human\species_types\shadowpeople.dm" +#include "code\modules\mob\living\carbon\human\species_types\skeletons.dm" +#include "code\modules\mob\living\carbon\human\species_types\synths.dm" +#include "code\modules\mob\living\carbon\human\species_types\vampire.dm" +#include "code\modules\mob\living\carbon\human\species_types\zombies.dm" +#include "code\modules\mob\living\carbon\monkey\combat.dm" +#include "code\modules\mob\living\carbon\monkey\death.dm" +#include "code\modules\mob\living\carbon\monkey\inventory.dm" +#include "code\modules\mob\living\carbon\monkey\life.dm" +#include "code\modules\mob\living\carbon\monkey\monkey.dm" +#include "code\modules\mob\living\carbon\monkey\monkey_defense.dm" +#include "code\modules\mob\living\carbon\monkey\punpun.dm" +#include "code\modules\mob\living\carbon\monkey\update_icons.dm" +#include "code\modules\mob\living\silicon\damage_procs.dm" +#include "code\modules\mob\living\silicon\death.dm" +#include "code\modules\mob\living\silicon\examine.dm" +#include "code\modules\mob\living\silicon\laws.dm" +#include "code\modules\mob\living\silicon\login.dm" +#include "code\modules\mob\living\silicon\say.dm" +#include "code\modules\mob\living\silicon\silicon.dm" +#include "code\modules\mob\living\silicon\silicon_defense.dm" +#include "code\modules\mob\living\silicon\silicon_movement.dm" +#include "code\modules\mob\living\silicon\ai\ai.dm" +#include "code\modules\mob\living\silicon\ai\ai_defense.dm" +#include "code\modules\mob\living\silicon\ai\death.dm" +#include "code\modules\mob\living\silicon\ai\examine.dm" +#include "code\modules\mob\living\silicon\ai\laws.dm" +#include "code\modules\mob\living\silicon\ai\life.dm" +#include "code\modules\mob\living\silicon\ai\login.dm" +#include "code\modules\mob\living\silicon\ai\logout.dm" +#include "code\modules\mob\living\silicon\ai\multicam.dm" +#include "code\modules\mob\living\silicon\ai\say.dm" +#include "code\modules\mob\living\silicon\ai\vox_sounds.dm" +#include "code\modules\mob\living\silicon\ai\freelook\cameranet.dm" +#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm" +#include "code\modules\mob\living\silicon\ai\freelook\eye.dm" +#include "code\modules\mob\living\silicon\ai\freelook\read_me.dm" +#include "code\modules\mob\living\silicon\pai\death.dm" +#include "code\modules\mob\living\silicon\pai\pai.dm" +#include "code\modules\mob\living\silicon\pai\pai_defense.dm" +#include "code\modules\mob\living\silicon\pai\pai_shell.dm" +#include "code\modules\mob\living\silicon\pai\personality.dm" +#include "code\modules\mob\living\silicon\pai\say.dm" +#include "code\modules\mob\living\silicon\pai\software.dm" +#include "code\modules\mob\living\silicon\robot\death.dm" +#include "code\modules\mob\living\silicon\robot\emote.dm" +#include "code\modules\mob\living\silicon\robot\examine.dm" +#include "code\modules\mob\living\silicon\robot\inventory.dm" +#include "code\modules\mob\living\silicon\robot\laws.dm" +#include "code\modules\mob\living\silicon\robot\life.dm" +#include "code\modules\mob\living\silicon\robot\login.dm" +#include "code\modules\mob\living\silicon\robot\robot.dm" +#include "code\modules\mob\living\silicon\robot\robot_defense.dm" +#include "code\modules\mob\living\silicon\robot\robot_modules.dm" +#include "code\modules\mob\living\silicon\robot\robot_movement.dm" +#include "code\modules\mob\living\silicon\robot\say.dm" +#include "code\modules\mob\living\simple_animal\animal_defense.dm" +#include "code\modules\mob\living\simple_animal\astral.dm" +#include "code\modules\mob\living\simple_animal\constructs.dm" +#include "code\modules\mob\living\simple_animal\corpse.dm" +#include "code\modules\mob\living\simple_animal\damage_procs.dm" +#include "code\modules\mob\living\simple_animal\parrot.dm" +#include "code\modules\mob\living\simple_animal\shade.dm" +#include "code\modules\mob\living\simple_animal\simple_animal.dm" +#include "code\modules\mob\living\simple_animal\simple_animal_vr.dm" +#include "code\modules\mob\living\simple_animal\status_procs.dm" +#include "code\modules\mob\living\simple_animal\bot\bot.dm" +#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm" +#include "code\modules\mob\living\simple_animal\bot\construction.dm" +#include "code\modules\mob\living\simple_animal\bot\ed209bot.dm" +#include "code\modules\mob\living\simple_animal\bot\firebot.dm" +#include "code\modules\mob\living\simple_animal\bot\floorbot.dm" +#include "code\modules\mob\living\simple_animal\bot\honkbot.dm" +#include "code\modules\mob\living\simple_animal\bot\medbot.dm" +#include "code\modules\mob\living\simple_animal\bot\mulebot.dm" +#include "code\modules\mob\living\simple_animal\bot\secbot.dm" +#include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm" +#include "code\modules\mob\living\simple_animal\friendly\butterfly.dm" +#include "code\modules\mob\living\simple_animal\friendly\cat.dm" +#include "code\modules\mob\living\simple_animal\friendly\cockroach.dm" +#include "code\modules\mob\living\simple_animal\friendly\crab.dm" +#include "code\modules\mob\living\simple_animal\friendly\dog.dm" +#include "code\modules\mob\living\simple_animal\friendly\farm_animals.dm" +#include "code\modules\mob\living\simple_animal\friendly\fox.dm" +#include "code\modules\mob\living\simple_animal\friendly\gondola.dm" +#include "code\modules\mob\living\simple_animal\friendly\lizard.dm" +#include "code\modules\mob\living\simple_animal\friendly\mouse.dm" +#include "code\modules\mob\living\simple_animal\friendly\panda.dm" +#include "code\modules\mob\living\simple_animal\friendly\penguin.dm" +#include "code\modules\mob\living\simple_animal\friendly\pet.dm" +#include "code\modules\mob\living\simple_animal\friendly\sloth.dm" +#include "code\modules\mob\living\simple_animal\friendly\snake.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\_drone.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\drones_as_items.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\extra_drone_types.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\interaction.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\inventory.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\say.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\verbs.dm" +#include "code\modules\mob\living\simple_animal\friendly\drone\visuals_icons.dm" +#include "code\modules\mob\living\simple_animal\guardian\guardian.dm" +#include "code\modules\mob\living\simple_animal\guardian\guardiannaming.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\assassin.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\charger.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\dextrous.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\explosive.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\fire.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\lightning.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\protector.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\ranged.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\standard.dm" +#include "code\modules\mob\living\simple_animal\guardian\types\support.dm" +#include "code\modules\mob\living\simple_animal\hostile\alien.dm" +#include "code\modules\mob\living\simple_animal\hostile\bear.dm" +#include "code\modules\mob\living\simple_animal\hostile\bees.dm" +#include "code\modules\mob\living\simple_animal\hostile\carp.dm" +#include "code\modules\mob\living\simple_animal\hostile\cat_butcher.dm" +#include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" +#include "code\modules\mob\living\simple_animal\hostile\faithless.dm" +#include "code\modules\mob\living\simple_animal\hostile\floor_cluwne.dm" +#include "code\modules\mob\living\simple_animal\hostile\giant_spider.dm" +#include "code\modules\mob\living\simple_animal\hostile\goose.dm" +#include "code\modules\mob\living\simple_animal\hostile\headcrab.dm" +#include "code\modules\mob\living\simple_animal\hostile\hivebot.dm" +#include "code\modules\mob\living\simple_animal\hostile\hostile.dm" +#include "code\modules\mob\living\simple_animal\hostile\illusion.dm" +#include "code\modules\mob\living\simple_animal\hostile\killertomato.dm" +#include "code\modules\mob\living\simple_animal\hostile\mecha_pilot.dm" +#include "code\modules\mob\living\simple_animal\hostile\mimic.dm" +#include "code\modules\mob\living\simple_animal\hostile\mushroom.dm" +#include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm" +#include "code\modules\mob\living\simple_animal\hostile\netherworld.dm" +#include "code\modules\mob\living\simple_animal\hostile\pirate.dm" +#include "code\modules\mob\living\simple_animal\hostile\russian.dm" +#include "code\modules\mob\living\simple_animal\hostile\sharks.dm" +#include "code\modules\mob\living\simple_animal\hostile\skeleton.dm" +#include "code\modules\mob\living\simple_animal\hostile\statue.dm" +#include "code\modules\mob\living\simple_animal\hostile\stickman.dm" +#include "code\modules\mob\living\simple_animal\hostile\syndicate.dm" +#include "code\modules\mob\living\simple_animal\hostile\tree.dm" +#include "code\modules\mob\living\simple_animal\hostile\venus_human_trap.dm" +#include "code\modules\mob\living\simple_animal\hostile\wizard.dm" +#include "code\modules\mob\living\simple_animal\hostile\wumborian_fugu.dm" +#include "code\modules\mob\living\simple_animal\hostile\zombie.dm" +#include "code\modules\mob\living\simple_animal\hostile\bosses\boss.dm" +#include "code\modules\mob\living\simple_animal\hostile\bosses\paperwizard.dm" +#include "code\modules\mob\living\simple_animal\hostile\gorilla\emotes.dm" +#include "code\modules\mob\living\simple_animal\hostile\gorilla\gorilla.dm" +#include "code\modules\mob\living\simple_animal\hostile\gorilla\visuals_icons.dm" +#include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" +#include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" +#include "code\modules\mob\living\simple_animal\hostile\jungle\mega_arachnid.dm" +#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm" +#include "code\modules\mob\living\simple_animal\hostile\jungle\seedling.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\colossus.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\dragon_vore.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\drake.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\megafauna.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\swarmer.dm" +#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\basilisk.dm" +#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" +#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\goldgrub.dm" +#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\goliath.dm" +#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm" +#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\hivelord.dm" +#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm" +#include "code\modules\mob\living\simple_animal\hostile\retaliate\bat.dm" +#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm" +#include "code\modules\mob\living\simple_animal\hostile\retaliate\frog.dm" +#include "code\modules\mob\living\simple_animal\hostile\retaliate\ghost.dm" +#include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm" +#include "code\modules\mob\living\simple_animal\hostile\retaliate\spaceman.dm" +#include "code\modules\mob\living\simple_animal\slime\death.dm" +#include "code\modules\mob\living\simple_animal\slime\emote.dm" +#include "code\modules\mob\living\simple_animal\slime\life.dm" +#include "code\modules\mob\living\simple_animal\slime\powers.dm" +#include "code\modules\mob\living\simple_animal\slime\say.dm" +#include "code\modules\mob\living\simple_animal\slime\slime.dm" +#include "code\modules\mob\living\simple_animal\slime\subtypes.dm" +#include "code\modules\modular_computers\laptop_vendor.dm" +#include "code\modules\modular_computers\computers\item\computer.dm" +#include "code\modules\modular_computers\computers\item\computer_components.dm" +#include "code\modules\modular_computers\computers\item\computer_damage.dm" +#include "code\modules\modular_computers\computers\item\computer_power.dm" +#include "code\modules\modular_computers\computers\item\computer_ui.dm" +#include "code\modules\modular_computers\computers\item\laptop.dm" +#include "code\modules\modular_computers\computers\item\laptop_presets.dm" +#include "code\modules\modular_computers\computers\item\processor.dm" +#include "code\modules\modular_computers\computers\item\tablet.dm" +#include "code\modules\modular_computers\computers\item\tablet_presets.dm" +#include "code\modules\modular_computers\computers\machinery\console_presets.dm" +#include "code\modules\modular_computers\computers\machinery\modular_computer.dm" +#include "code\modules\modular_computers\computers\machinery\modular_console.dm" +#include "code\modules\modular_computers\file_system\computer_file.dm" +#include "code\modules\modular_computers\file_system\data.dm" +#include "code\modules\modular_computers\file_system\program.dm" +#include "code\modules\modular_computers\file_system\program_events.dm" +#include "code\modules\modular_computers\file_system\programs\airestorer.dm" +#include "code\modules\modular_computers\file_system\programs\alarm.dm" +#include "code\modules\modular_computers\file_system\programs\card.dm" +#include "code\modules\modular_computers\file_system\programs\configurator.dm" +#include "code\modules\modular_computers\file_system\programs\file_browser.dm" +#include "code\modules\modular_computers\file_system\programs\ntdownloader.dm" +#include "code\modules\modular_computers\file_system\programs\ntmonitor.dm" +#include "code\modules\modular_computers\file_system\programs\ntnrc_client.dm" +#include "code\modules\modular_computers\file_system\programs\nttransfer.dm" +#include "code\modules\modular_computers\file_system\programs\powermonitor.dm" +#include "code\modules\modular_computers\file_system\programs\sm_monitor.dm" +#include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm" +#include "code\modules\modular_computers\file_system\programs\antagonist\revelation.dm" +#include "code\modules\modular_computers\hardware\_hardware.dm" +#include "code\modules\modular_computers\hardware\ai_slot.dm" +#include "code\modules\modular_computers\hardware\battery_module.dm" +#include "code\modules\modular_computers\hardware\card_slot.dm" +#include "code\modules\modular_computers\hardware\CPU.dm" +#include "code\modules\modular_computers\hardware\hard_drive.dm" +#include "code\modules\modular_computers\hardware\network_card.dm" +#include "code\modules\modular_computers\hardware\portable_disk.dm" +#include "code\modules\modular_computers\hardware\printer.dm" +#include "code\modules\modular_computers\hardware\recharger.dm" +#include "code\modules\modular_computers\NTNet\NTNRC\conversation.dm" +#include "code\modules\ninja\__ninjaDefines.dm" +#include "code\modules\ninja\energy_katana.dm" +#include "code\modules\ninja\ninja_event.dm" +#include "code\modules\ninja\outfit.dm" +#include "code\modules\ninja\suit\gloves.dm" +#include "code\modules\ninja\suit\head.dm" +#include "code\modules\ninja\suit\mask.dm" +#include "code\modules\ninja\suit\ninjaDrainAct.dm" +#include "code\modules\ninja\suit\shoes.dm" +#include "code\modules\ninja\suit\suit.dm" +#include "code\modules\ninja\suit\suit_attackby.dm" +#include "code\modules\ninja\suit\suit_initialisation.dm" +#include "code\modules\ninja\suit\suit_process.dm" +#include "code\modules\ninja\suit\n_suit_verbs\energy_net_nets.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_adrenaline.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_cost_check.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_empulse.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_net.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_smoke.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_stars.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_stealth.dm" +#include "code\modules\ninja\suit\n_suit_verbs\ninja_sword_recall.dm" +#include "code\modules\NTNet\netdata.dm" +#include "code\modules\NTNet\network.dm" +#include "code\modules\NTNet\relays.dm" +#include "code\modules\NTNet\services\_service.dm" +#include "code\modules\oracle_ui\assets.dm" +#include "code\modules\oracle_ui\hookup_procs.dm" +#include "code\modules\oracle_ui\oracle_ui.dm" +#include "code\modules\oracle_ui\themed.dm" +#include "code\modules\paperwork\clipboard.dm" +#include "code\modules\paperwork\contract.dm" +#include "code\modules\paperwork\filingcabinet.dm" +#include "code\modules\paperwork\folders.dm" +#include "code\modules\paperwork\handlabeler.dm" +#include "code\modules\paperwork\paper.dm" +#include "code\modules\paperwork\paper_cutter.dm" +#include "code\modules\paperwork\paper_premade.dm" +#include "code\modules\paperwork\paperbin.dm" +#include "code\modules\paperwork\paperplane.dm" +#include "code\modules\paperwork\pen.dm" +#include "code\modules\paperwork\photocopier.dm" +#include "code\modules\paperwork\stamps.dm" +#include "code\modules\photography\_pictures.dm" +#include "code\modules\photography\camera\camera.dm" +#include "code\modules\photography\camera\camera_image_capturing.dm" +#include "code\modules\photography\camera\film.dm" +#include "code\modules\photography\camera\other.dm" +#include "code\modules\photography\camera\silicon_camera.dm" +#include "code\modules\photography\photos\album.dm" +#include "code\modules\photography\photos\frame.dm" +#include "code\modules\photography\photos\photo.dm" +#include "code\modules\pool\pool_controller.dm" +#include "code\modules\pool\pool_drain.dm" +#include "code\modules\pool\pool_effects.dm" +#include "code\modules\pool\pool_main.dm" +#include "code\modules\pool\pool_noodles.dm" +#include "code\modules\pool\pool_structures.dm" +#include "code\modules\pool\pool_wires.dm" +#include "code\modules\power\apc.dm" +#include "code\modules\power\cable.dm" +#include "code\modules\power\cell.dm" +#include "code\modules\power\floodlight.dm" +#include "code\modules\power\generator.dm" +#include "code\modules\power\gravitygenerator.dm" +#include "code\modules\power\lighting.dm" +#include "code\modules\power\monitor.dm" +#include "code\modules\power\multiz.dm" +#include "code\modules\power\port_gen.dm" +#include "code\modules\power\power.dm" +#include "code\modules\power\powernet.dm" +#include "code\modules\power\rtg.dm" +#include "code\modules\power\smes.dm" +#include "code\modules\power\solar.dm" +#include "code\modules\power\terminal.dm" +#include "code\modules\power\tracker.dm" +#include "code\modules\power\turbine.dm" +#include "code\modules\power\antimatter\containment_jar.dm" +#include "code\modules\power\antimatter\control.dm" +#include "code\modules\power\antimatter\shielding.dm" +#include "code\modules\power\singularity\collector.dm" +#include "code\modules\power\singularity\containment_field.dm" +#include "code\modules\power\singularity\emitter.dm" +#include "code\modules\power\singularity\field_generator.dm" +#include "code\modules\power\singularity\generator.dm" +#include "code\modules\power\singularity\investigate.dm" +#include "code\modules\power\singularity\narsie.dm" +#include "code\modules\power\singularity\singularity.dm" +#include "code\modules\power\singularity\particle_accelerator\particle.dm" +#include "code\modules\power\singularity\particle_accelerator\particle_accelerator.dm" +#include "code\modules\power\singularity\particle_accelerator\particle_control.dm" +#include "code\modules\power\singularity\particle_accelerator\particle_emitter.dm" +#include "code\modules\power\supermatter\supermatter.dm" +#include "code\modules\power\tesla\coil.dm" +#include "code\modules\power\tesla\energy_ball.dm" +#include "code\modules\power\tesla\generator.dm" +#include "code\modules\procedural_mapping\mapGenerator.dm" +#include "code\modules\procedural_mapping\mapGeneratorModule.dm" +#include "code\modules\procedural_mapping\mapGeneratorObj.dm" +#include "code\modules\procedural_mapping\mapGeneratorReadme.dm" +#include "code\modules\procedural_mapping\mapGeneratorModules\helpers.dm" +#include "code\modules\procedural_mapping\mapGeneratorModules\nature.dm" +#include "code\modules\procedural_mapping\mapGenerators\asteroid.dm" +#include "code\modules\procedural_mapping\mapGenerators\cellular.dm" +#include "code\modules\procedural_mapping\mapGenerators\cult.dm" +#include "code\modules\procedural_mapping\mapGenerators\lava_river.dm" +#include "code\modules\procedural_mapping\mapGenerators\lavaland.dm" +#include "code\modules\procedural_mapping\mapGenerators\nature.dm" +#include "code\modules\procedural_mapping\mapGenerators\repair.dm" +#include "code\modules\procedural_mapping\mapGenerators\shuttle.dm" +#include "code\modules\procedural_mapping\mapGenerators\syndicate.dm" +#include "code\modules\projectiles\gun.dm" +#include "code\modules\projectiles\pins.dm" +#include "code\modules\projectiles\projectile.dm" +#include "code\modules\projectiles\ammunition\_ammunition.dm" +#include "code\modules\projectiles\ammunition\_firing.dm" +#include "code\modules\projectiles\ammunition\ballistic\lmg.dm" +#include "code\modules\projectiles\ammunition\ballistic\pistol.dm" +#include "code\modules\projectiles\ammunition\ballistic\revolver.dm" +#include "code\modules\projectiles\ammunition\ballistic\rifle.dm" +#include "code\modules\projectiles\ammunition\ballistic\shotgun.dm" +#include "code\modules\projectiles\ammunition\ballistic\smg.dm" +#include "code\modules\projectiles\ammunition\ballistic\sniper.dm" +#include "code\modules\projectiles\ammunition\caseless\_caseless.dm" +#include "code\modules\projectiles\ammunition\caseless\foam.dm" +#include "code\modules\projectiles\ammunition\caseless\misc.dm" +#include "code\modules\projectiles\ammunition\caseless\rocket.dm" +#include "code\modules\projectiles\ammunition\energy\_energy.dm" +#include "code\modules\projectiles\ammunition\energy\ebow.dm" +#include "code\modules\projectiles\ammunition\energy\gravity.dm" +#include "code\modules\projectiles\ammunition\energy\laser.dm" +#include "code\modules\projectiles\ammunition\energy\lmg.dm" +#include "code\modules\projectiles\ammunition\energy\plasma.dm" +#include "code\modules\projectiles\ammunition\energy\plasma_cit.dm" +#include "code\modules\projectiles\ammunition\energy\portal.dm" +#include "code\modules\projectiles\ammunition\energy\special.dm" +#include "code\modules\projectiles\ammunition\energy\stun.dm" +#include "code\modules\projectiles\ammunition\special\magic.dm" +#include "code\modules\projectiles\ammunition\special\syringe.dm" +#include "code\modules\projectiles\boxes_magazines\_box_magazine.dm" +#include "code\modules\projectiles\boxes_magazines\ammo_boxes.dm" +#include "code\modules\projectiles\boxes_magazines\external\grenade.dm" +#include "code\modules\projectiles\boxes_magazines\external\lmg.dm" +#include "code\modules\projectiles\boxes_magazines\external\pistol.dm" +#include "code\modules\projectiles\boxes_magazines\external\rechargable.dm" +#include "code\modules\projectiles\boxes_magazines\external\rifle.dm" +#include "code\modules\projectiles\boxes_magazines\external\shotgun.dm" +#include "code\modules\projectiles\boxes_magazines\external\smg.dm" +#include "code\modules\projectiles\boxes_magazines\external\sniper.dm" +#include "code\modules\projectiles\boxes_magazines\external\toy.dm" +#include "code\modules\projectiles\boxes_magazines\internal\_cylinder.dm" +#include "code\modules\projectiles\boxes_magazines\internal\_internal.dm" +#include "code\modules\projectiles\boxes_magazines\internal\grenade.dm" +#include "code\modules\projectiles\boxes_magazines\internal\misc.dm" +#include "code\modules\projectiles\boxes_magazines\internal\revolver.dm" +#include "code\modules\projectiles\boxes_magazines\internal\rifle.dm" +#include "code\modules\projectiles\boxes_magazines\internal\shotgun.dm" +#include "code\modules\projectiles\boxes_magazines\internal\toy.dm" +#include "code\modules\projectiles\guns\ballistic.dm" +#include "code\modules\projectiles\guns\energy.dm" +#include "code\modules\projectiles\guns\magic.dm" +#include "code\modules\projectiles\guns\ballistic\automatic.dm" +#include "code\modules\projectiles\guns\ballistic\laser_gatling.dm" +#include "code\modules\projectiles\guns\ballistic\launchers.dm" +#include "code\modules\projectiles\guns\ballistic\pistol.dm" +#include "code\modules\projectiles\guns\ballistic\revolver.dm" +#include "code\modules\projectiles\guns\ballistic\shotgun.dm" +#include "code\modules\projectiles\guns\ballistic\toy.dm" +#include "code\modules\projectiles\guns\energy\dueling.dm" +#include "code\modules\projectiles\guns\energy\energy_gun.dm" +#include "code\modules\projectiles\guns\energy\kinetic_accelerator.dm" +#include "code\modules\projectiles\guns\energy\laser.dm" +#include "code\modules\projectiles\guns\energy\megabuster.dm" +#include "code\modules\projectiles\guns\energy\mounted.dm" +#include "code\modules\projectiles\guns\energy\plasma_cit.dm" +#include "code\modules\projectiles\guns\energy\pulse.dm" +#include "code\modules\projectiles\guns\energy\special.dm" +#include "code\modules\projectiles\guns\energy\stun.dm" +#include "code\modules\projectiles\guns\magic\staff.dm" +#include "code\modules\projectiles\guns\magic\wand.dm" +#include "code\modules\projectiles\guns\misc\beam_rifle.dm" +#include "code\modules\projectiles\guns\misc\blastcannon.dm" +#include "code\modules\projectiles\guns\misc\chem_gun.dm" +#include "code\modules\projectiles\guns\misc\grenade_launcher.dm" +#include "code\modules\projectiles\guns\misc\medbeam.dm" +#include "code\modules\projectiles\guns\misc\syringe_gun.dm" +#include "code\modules\projectiles\projectile\beams.dm" +#include "code\modules\projectiles\projectile\bullets.dm" +#include "code\modules\projectiles\projectile\magic.dm" +#include "code\modules\projectiles\projectile\megabuster.dm" +#include "code\modules\projectiles\projectile\plasma.dm" +#include "code\modules\projectiles\projectile\bullets\_incendiary.dm" +#include "code\modules\projectiles\projectile\bullets\dart_syringe.dm" +#include "code\modules\projectiles\projectile\bullets\dnainjector.dm" +#include "code\modules\projectiles\projectile\bullets\grenade.dm" +#include "code\modules\projectiles\projectile\bullets\lmg.dm" +#include "code\modules\projectiles\projectile\bullets\pistol.dm" +#include "code\modules\projectiles\projectile\bullets\revolver.dm" +#include "code\modules\projectiles\projectile\bullets\rifle.dm" +#include "code\modules\projectiles\projectile\bullets\shotgun.dm" +#include "code\modules\projectiles\projectile\bullets\smg.dm" +#include "code\modules\projectiles\projectile\bullets\sniper.dm" +#include "code\modules\projectiles\projectile\bullets\special.dm" +#include "code\modules\projectiles\projectile\energy\_energy.dm" +#include "code\modules\projectiles\projectile\energy\ebow.dm" +#include "code\modules\projectiles\projectile\energy\misc.dm" +#include "code\modules\projectiles\projectile\energy\net_snare.dm" +#include "code\modules\projectiles\projectile\energy\nuclear_particle.dm" +#include "code\modules\projectiles\projectile\energy\stun.dm" +#include "code\modules\projectiles\projectile\energy\tesla.dm" +#include "code\modules\projectiles\projectile\magic\spellcard.dm" +#include "code\modules\projectiles\projectile\reusable\_reusable.dm" +#include "code\modules\projectiles\projectile\reusable\foam_dart.dm" +#include "code\modules\projectiles\projectile\reusable\magspear.dm" +#include "code\modules\projectiles\projectile\special\curse.dm" +#include "code\modules\projectiles\projectile\special\floral.dm" +#include "code\modules\projectiles\projectile\special\gravity.dm" +#include "code\modules\projectiles\projectile\special\hallucination.dm" +#include "code\modules\projectiles\projectile\special\ion.dm" +#include "code\modules\projectiles\projectile\special\meteor.dm" +#include "code\modules\projectiles\projectile\special\mindflayer.dm" +#include "code\modules\projectiles\projectile\special\neurotoxin.dm" +#include "code\modules\projectiles\projectile\special\plasma.dm" +#include "code\modules\projectiles\projectile\special\rocket.dm" +#include "code\modules\projectiles\projectile\special\temperature.dm" +#include "code\modules\projectiles\projectile\special\wormhole.dm" +#include "code\modules\reagents\chem_splash.dm" +#include "code\modules\reagents\chem_wiki_render.dm" +#include "code\modules\reagents\reagent_containers.dm" +#include "code\modules\reagents\reagent_dispenser.dm" +#include "code\modules\reagents\chemistry\colors.dm" +#include "code\modules\reagents\chemistry\holder.dm" +#include "code\modules\reagents\chemistry\reagents.dm" +#include "code\modules\reagents\chemistry\recipes.dm" +#include "code\modules\reagents\chemistry\machinery\chem_dispenser.dm" +#include "code\modules\reagents\chemistry\machinery\chem_heater.dm" +#include "code\modules\reagents\chemistry\machinery\chem_master.dm" +#include "code\modules\reagents\chemistry\machinery\chem_synthesizer.dm" +#include "code\modules\reagents\chemistry\machinery\pandemic.dm" +#include "code\modules\reagents\chemistry\machinery\reagentgrinder.dm" +#include "code\modules\reagents\chemistry\machinery\smoke_machine.dm" +#include "code\modules\reagents\chemistry\reagents\alcohol_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\blob_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\drink_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\drug_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\food_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\impure_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\medicine_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\other_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\pyrotechnic_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\toxin_reagents.dm" +#include "code\modules\reagents\chemistry\recipes\drugs.dm" +#include "code\modules\reagents\chemistry\recipes\medicine.dm" +#include "code\modules\reagents\chemistry\recipes\others.dm" +#include "code\modules\reagents\chemistry\recipes\pyrotechnics.dm" +#include "code\modules\reagents\chemistry\recipes\slime_extracts.dm" +#include "code\modules\reagents\chemistry\recipes\special.dm" +#include "code\modules\reagents\chemistry\recipes\toxins.dm" +#include "code\modules\reagents\reagent_containers\blood_pack.dm" +#include "code\modules\reagents\reagent_containers\borghydro.dm" +#include "code\modules\reagents\reagent_containers\bottle.dm" +#include "code\modules\reagents\reagent_containers\chem_pack.dm" +#include "code\modules\reagents\reagent_containers\Chemical_tongue.dm" +#include "code\modules\reagents\reagent_containers\dropper.dm" +#include "code\modules\reagents\reagent_containers\glass.dm" +#include "code\modules\reagents\reagent_containers\hypospray.dm" +#include "code\modules\reagents\reagent_containers\hypovial.dm" +#include "code\modules\reagents\reagent_containers\medspray.dm" +#include "code\modules\reagents\reagent_containers\patch.dm" +#include "code\modules\reagents\reagent_containers\pill.dm" +#include "code\modules\reagents\reagent_containers\rags.dm" +#include "code\modules\reagents\reagent_containers\sleeper_buffer.dm" +#include "code\modules\reagents\reagent_containers\spray.dm" +#include "code\modules\reagents\reagent_containers\syringes.dm" +#include "code\modules\recycling\conveyor2.dm" +#include "code\modules\recycling\sortingmachinery.dm" +#include "code\modules\recycling\disposal\bin.dm" +#include "code\modules\recycling\disposal\construction.dm" +#include "code\modules\recycling\disposal\eject.dm" +#include "code\modules\recycling\disposal\holder.dm" +#include "code\modules\recycling\disposal\multiz.dm" +#include "code\modules\recycling\disposal\outlet.dm" +#include "code\modules\recycling\disposal\pipe.dm" +#include "code\modules\recycling\disposal\pipe_sorting.dm" +#include "code\modules\research\designs.dm" +#include "code\modules\research\destructive_analyzer.dm" +#include "code\modules\research\experimentor.dm" +#include "code\modules\research\rdconsole.dm" +#include "code\modules\research\rdmachines.dm" +#include "code\modules\research\research_disk.dm" +#include "code\modules\research\server.dm" +#include "code\modules\research\stock_parts.dm" +#include "code\modules\research\designs\AI_module_designs.dm" +#include "code\modules\research\designs\autobotter_designs.dm" +#include "code\modules\research\designs\biogenerator_designs.dm" +#include "code\modules\research\designs\bluespace_designs.dm" +#include "code\modules\research\designs\computer_part_designs.dm" +#include "code\modules\research\designs\electronics_designs.dm" +#include "code\modules\research\designs\equipment_designs.dm" +#include "code\modules\research\designs\limbgrower_designs.dm" +#include "code\modules\research\designs\mecha_designs.dm" +#include "code\modules\research\designs\mechfabricator_designs.dm" +#include "code\modules\research\designs\medical_designs.dm" +#include "code\modules\research\designs\mining_designs.dm" +#include "code\modules\research\designs\misc_designs.dm" +#include "code\modules\research\designs\nanite_designs.dm" +#include "code\modules\research\designs\power_designs.dm" +#include "code\modules\research\designs\smelting_designs.dm" +#include "code\modules\research\designs\stock_parts_designs.dm" +#include "code\modules\research\designs\telecomms_designs.dm" +#include "code\modules\research\designs\weapon_designs.dm" +#include "code\modules\research\designs\autolathe_desings\autolathe_designs_construction.dm" +#include "code\modules\research\designs\autolathe_desings\autolathe_designs_electronics.dm" +#include "code\modules\research\designs\autolathe_desings\autolathe_designs_medical_and_dinnerware.dm" +#include "code\modules\research\designs\autolathe_desings\autolathe_designs_sec_and_hacked.dm" +#include "code\modules\research\designs\autolathe_desings\autolathe_designs_tcomms_and_misc.dm" +#include "code\modules\research\designs\autolathe_desings\autolathe_designs_tools.dm" +#include "code\modules\research\designs\comp_board_designs\comp_board_designs_all_misc.dm" +#include "code\modules\research\designs\comp_board_designs\comp_board_designs_cargo .dm" +#include "code\modules\research\designs\comp_board_designs\comp_board_designs_engi.dm" +#include "code\modules\research\designs\comp_board_designs\comp_board_designs_medical.dm" +#include "code\modules\research\designs\comp_board_designs\comp_board_designs_sci.dm" +#include "code\modules\research\designs\comp_board_designs\comp_board_designs_sec.dm" +#include "code\modules\research\designs\machine_desings\machine_designs_all_misc.dm" +#include "code\modules\research\designs\machine_desings\machine_designs_cargo.dm" +#include "code\modules\research\designs\machine_desings\machine_designs_engi.dm" +#include "code\modules\research\designs\machine_desings\machine_designs_medical.dm" +#include "code\modules\research\designs\machine_desings\machine_designs_sci.dm" +#include "code\modules\research\designs\machine_desings\machine_designs_service.dm" +#include "code\modules\research\machinery\_production.dm" +#include "code\modules\research\machinery\circuit_imprinter.dm" +#include "code\modules\research\machinery\departmental_circuit_imprinter.dm" +#include "code\modules\research\machinery\departmental_protolathe.dm" +#include "code\modules\research\machinery\departmental_techfab.dm" +#include "code\modules\research\machinery\protolathe.dm" +#include "code\modules\research\machinery\techfab.dm" +#include "code\modules\research\nanites\nanite_chamber.dm" +#include "code\modules\research\nanites\nanite_chamber_computer.dm" +#include "code\modules\research\nanites\nanite_cloud_controller.dm" +#include "code\modules\research\nanites\nanite_hijacker.dm" +#include "code\modules\research\nanites\nanite_misc_items.dm" +#include "code\modules\research\nanites\nanite_program_hub.dm" +#include "code\modules\research\nanites\nanite_programmer.dm" +#include "code\modules\research\nanites\nanite_programs.dm" +#include "code\modules\research\nanites\nanite_remote.dm" +#include "code\modules\research\nanites\program_disks.dm" +#include "code\modules\research\nanites\public_chamber.dm" +#include "code\modules\research\nanites\nanite_programs\buffing.dm" +#include "code\modules\research\nanites\nanite_programs\healing.dm" +#include "code\modules\research\nanites\nanite_programs\rogue.dm" +#include "code\modules\research\nanites\nanite_programs\sensor.dm" +#include "code\modules\research\nanites\nanite_programs\suppression.dm" +#include "code\modules\research\nanites\nanite_programs\utility.dm" +#include "code\modules\research\nanites\nanite_programs\weapon.dm" +#include "code\modules\research\techweb\__techweb_helpers.dm" +#include "code\modules\research\techweb\_techweb.dm" +#include "code\modules\research\techweb\_techweb_node.dm" +#include "code\modules\research\techweb\all_nodes.dm" +#include "code\modules\research\xenoarch\artifact.dm" +#include "code\modules\research\xenoarch\artifact_list.dm" +#include "code\modules\research\xenoarch\strange_rock.dm" +#include "code\modules\research\xenoarch\tools.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\amauri.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\gelthi.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\jurlmah.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\nofruit.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\shand.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\surik.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\telriis.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\thaadra.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\vale.dm" +#include "code\modules\research\xenoarch\xenobotany\grown\vaporsac.dm" +#include "code\modules\research\xenobiology\xenobio_camera.dm" +#include "code\modules\research\xenobiology\xenobiology.dm" +#include "code\modules\research\xenobiology\crossbreeding\__corecross.dm" +#include "code\modules\research\xenobiology\crossbreeding\_clothing.dm" +#include "code\modules\research\xenobiology\crossbreeding\_misc.dm" +#include "code\modules\research\xenobiology\crossbreeding\_mobs.dm" +#include "code\modules\research\xenobiology\crossbreeding\_status_effects.dm" +#include "code\modules\research\xenobiology\crossbreeding\_weapons.dm" +#include "code\modules\research\xenobiology\crossbreeding\burning.dm" +#include "code\modules\research\xenobiology\crossbreeding\charged.dm" +#include "code\modules\research\xenobiology\crossbreeding\chilling.dm" +#include "code\modules\research\xenobiology\crossbreeding\consuming.dm" +#include "code\modules\research\xenobiology\crossbreeding\industrial.dm" +#include "code\modules\research\xenobiology\crossbreeding\prismatic.dm" +#include "code\modules\research\xenobiology\crossbreeding\recurring.dm" +#include "code\modules\research\xenobiology\crossbreeding\regenerative.dm" +#include "code\modules\research\xenobiology\crossbreeding\reproductive.dm" +#include "code\modules\research\xenobiology\crossbreeding\selfsustaining.dm" +#include "code\modules\research\xenobiology\crossbreeding\stabilized.dm" +#include "code\modules\ruins\lavaland_ruin_code.dm" +#include "code\modules\ruins\lavalandruin_code\biodome_clown_planet.dm" +#include "code\modules\ruins\lavalandruin_code\pizzaparty.dm" +#include "code\modules\ruins\lavalandruin_code\puzzle.dm" +#include "code\modules\ruins\lavalandruin_code\sloth.dm" +#include "code\modules\ruins\lavalandruin_code\surface.dm" +#include "code\modules\ruins\lavalandruin_code\syndicate_base.dm" +#include "code\modules\ruins\objects_and_mobs\ash_walker_den.dm" +#include "code\modules\ruins\objects_and_mobs\necropolis_gate.dm" +#include "code\modules\ruins\objects_and_mobs\sin_ruins.dm" +#include "code\modules\ruins\spaceruin_code\asteroid4.dm" +#include "code\modules\ruins\spaceruin_code\bigderelict1.dm" +#include "code\modules\ruins\spaceruin_code\caravanambush.dm" +#include "code\modules\ruins\spaceruin_code\cloning_lab.dm" +#include "code\modules\ruins\spaceruin_code\crashedclownship.dm" +#include "code\modules\ruins\spaceruin_code\crashedship.dm" +#include "code\modules\ruins\spaceruin_code\deepstorage.dm" +#include "code\modules\ruins\spaceruin_code\DJstation.dm" +#include "code\modules\ruins\spaceruin_code\hilbertshotel.dm" +#include "code\modules\ruins\spaceruin_code\listeningstation.dm" +#include "code\modules\ruins\spaceruin_code\miracle.dm" +#include "code\modules\ruins\spaceruin_code\oldstation.dm" +#include "code\modules\ruins\spaceruin_code\originalcontent.dm" +#include "code\modules\ruins\spaceruin_code\spacehotel.dm" +#include "code\modules\ruins\spaceruin_code\TheDerelict.dm" +#include "code\modules\ruins\spaceruin_code\whiteshipruin_box.dm" +#include "code\modules\security_levels\keycard_authentication.dm" +#include "code\modules\security_levels\security_levels.dm" +#include "code\modules\shuttle\arrivals.dm" +#include "code\modules\shuttle\assault_pod.dm" +#include "code\modules\shuttle\computer.dm" +#include "code\modules\shuttle\docking.dm" +#include "code\modules\shuttle\elevator.dm" +#include "code\modules\shuttle\emergency.dm" +#include "code\modules\shuttle\ferry.dm" +#include "code\modules\shuttle\manipulator.dm" +#include "code\modules\shuttle\monastery.dm" +#include "code\modules\shuttle\navigation_computer.dm" +#include "code\modules\shuttle\on_move.dm" +#include "code\modules\shuttle\ripple.dm" +#include "code\modules\shuttle\shuttle.dm" +#include "code\modules\shuttle\shuttle_rotate.dm" +#include "code\modules\shuttle\special.dm" +#include "code\modules\shuttle\supply.dm" +#include "code\modules\shuttle\syndicate.dm" +#include "code\modules\shuttle\white_ship.dm" +#include "code\modules\spells\spell.dm" +#include "code\modules\spells\spell_types\adminbussed.dm" +#include "code\modules\spells\spell_types\aimed.dm" +#include "code\modules\spells\spell_types\area_teleport.dm" +#include "code\modules\spells\spell_types\barnyard.dm" +#include "code\modules\spells\spell_types\bloodcrawl.dm" +#include "code\modules\spells\spell_types\charge.dm" +#include "code\modules\spells\spell_types\conjure.dm" +#include "code\modules\spells\spell_types\construct_spells.dm" +#include "code\modules\spells\spell_types\devil.dm" +#include "code\modules\spells\spell_types\devil_boons.dm" +#include "code\modules\spells\spell_types\dumbfire.dm" +#include "code\modules\spells\spell_types\emplosion.dm" +#include "code\modules\spells\spell_types\ethereal_jaunt.dm" +#include "code\modules\spells\spell_types\explosion.dm" +#include "code\modules\spells\spell_types\forcewall.dm" +#include "code\modules\spells\spell_types\genetic.dm" +#include "code\modules\spells\spell_types\godhand.dm" +#include "code\modules\spells\spell_types\infinite_guns.dm" +#include "code\modules\spells\spell_types\inflict_handler.dm" +#include "code\modules\spells\spell_types\knock.dm" +#include "code\modules\spells\spell_types\lichdom.dm" +#include "code\modules\spells\spell_types\lightning.dm" +#include "code\modules\spells\spell_types\mime.dm" +#include "code\modules\spells\spell_types\mind_transfer.dm" +#include "code\modules\spells\spell_types\projectile.dm" +#include "code\modules\spells\spell_types\rightandwrong.dm" +#include "code\modules\spells\spell_types\rod_form.dm" +#include "code\modules\spells\spell_types\santa.dm" +#include "code\modules\spells\spell_types\shadow_walk.dm" +#include "code\modules\spells\spell_types\shapeshift.dm" +#include "code\modules\spells\spell_types\spacetime_distortion.dm" +#include "code\modules\spells\spell_types\summonitem.dm" +#include "code\modules\spells\spell_types\taeclowndo.dm" +#include "code\modules\spells\spell_types\telepathy.dm" +#include "code\modules\spells\spell_types\the_traps.dm" +#include "code\modules\spells\spell_types\touch_attacks.dm" +#include "code\modules\spells\spell_types\trigger.dm" +#include "code\modules\spells\spell_types\turf_teleport.dm" +#include "code\modules\spells\spell_types\voice_of_god.dm" +#include "code\modules\spells\spell_types\wizard.dm" +#include "code\modules\station_goals\bsa.dm" +#include "code\modules\station_goals\dna_vault.dm" +#include "code\modules\station_goals\shield.dm" +#include "code\modules\station_goals\station_goal.dm" +#include "code\modules\surgery\amputation.dm" +#include "code\modules\surgery\brain_surgery.dm" +#include "code\modules\surgery\breast_augmentation.dm" +#include "code\modules\surgery\cavity_implant.dm" +#include "code\modules\surgery\core_removal.dm" +#include "code\modules\surgery\coronary_bypass.dm" +#include "code\modules\surgery\dental_implant.dm" +#include "code\modules\surgery\embalming.dm" +#include "code\modules\surgery\experimental_dissection.dm" +#include "code\modules\surgery\eye_surgery.dm" +#include "code\modules\surgery\graft_synthtissue.dm" +#include "code\modules\surgery\healing.dm" +#include "code\modules\surgery\helpers.dm" +#include "code\modules\surgery\implant_removal.dm" +#include "code\modules\surgery\limb_augmentation.dm" +#include "code\modules\surgery\lipoplasty.dm" +#include "code\modules\surgery\lobectomy.dm" +#include "code\modules\surgery\mechanic_steps.dm" +#include "code\modules\surgery\nutcracker.dm" +#include "code\modules\surgery\organ_manipulation.dm" +#include "code\modules\surgery\organic_steps.dm" +#include "code\modules\surgery\penis_augmentation.dm" +#include "code\modules\surgery\plastic_surgery.dm" +#include "code\modules\surgery\prosthetic_replacement.dm" +#include "code\modules\surgery\remove_embedded_object.dm" +#include "code\modules\surgery\surgery.dm" +#include "code\modules\surgery\surgery_step.dm" +#include "code\modules\surgery\tools.dm" +#include "code\modules\surgery\advanced\brainwashing.dm" +#include "code\modules\surgery\advanced\lobotomy.dm" +#include "code\modules\surgery\advanced\necrotic_revival.dm" +#include "code\modules\surgery\advanced\pacification.dm" +#include "code\modules\surgery\advanced\revival.dm" +#include "code\modules\surgery\advanced\toxichealing.dm" +#include "code\modules\surgery\advanced\viral_bonding.dm" +#include "code\modules\surgery\advanced\bioware\bioware.dm" +#include "code\modules\surgery\advanced\bioware\bioware_surgery.dm" +#include "code\modules\surgery\advanced\bioware\ligament_hook.dm" +#include "code\modules\surgery\advanced\bioware\ligament_reinforcement.dm" +#include "code\modules\surgery\advanced\bioware\muscled_veins.dm" +#include "code\modules\surgery\advanced\bioware\nerve_grounding.dm" +#include "code\modules\surgery\advanced\bioware\nerve_splicing.dm" +#include "code\modules\surgery\advanced\bioware\vein_threading.dm" +#include "code\modules\surgery\bodyparts\bodyparts.dm" +#include "code\modules\surgery\bodyparts\broken.dm" +#include "code\modules\surgery\bodyparts\dismemberment.dm" +#include "code\modules\surgery\bodyparts\head.dm" +#include "code\modules\surgery\bodyparts\helpers.dm" +#include "code\modules\surgery\bodyparts\robot_bodyparts.dm" +#include "code\modules\surgery\organs\appendix.dm" +#include "code\modules\surgery\organs\augments_arms.dm" +#include "code\modules\surgery\organs\augments_chest.dm" +#include "code\modules\surgery\organs\augments_eyes.dm" +#include "code\modules\surgery\organs\augments_internal.dm" +#include "code\modules\surgery\organs\autosurgeon.dm" +#include "code\modules\surgery\organs\ears.dm" +#include "code\modules\surgery\organs\eyes.dm" +#include "code\modules\surgery\organs\heart.dm" +#include "code\modules\surgery\organs\helpers.dm" +#include "code\modules\surgery\organs\liver.dm" +#include "code\modules\surgery\organs\lungs.dm" +#include "code\modules\surgery\organs\organ_internal.dm" +#include "code\modules\surgery\organs\stomach.dm" +#include "code\modules\surgery\organs\tails.dm" +#include "code\modules\surgery\organs\tongue.dm" +#include "code\modules\surgery\organs\vocal_cords.dm" +#include "code\modules\tgs\includes.dm" +#include "code\modules\tgui\external.dm" +#include "code\modules\tgui\states.dm" +#include "code\modules\tgui\subsystem.dm" +#include "code\modules\tgui\tgui.dm" +#include "code\modules\tgui\states\admin.dm" +#include "code\modules\tgui\states\always.dm" +#include "code\modules\tgui\states\conscious.dm" +#include "code\modules\tgui\states\contained.dm" +#include "code\modules\tgui\states\deep_inventory.dm" +#include "code\modules\tgui\states\default.dm" +#include "code\modules\tgui\states\hands.dm" +#include "code\modules\tgui\states\human_adjacent.dm" +#include "code\modules\tgui\states\inventory.dm" +#include "code\modules\tgui\states\language_menu.dm" +#include "code\modules\tgui\states\not_incapacitated.dm" +#include "code\modules\tgui\states\notcontained.dm" +#include "code\modules\tgui\states\observer.dm" +#include "code\modules\tgui\states\physical.dm" +#include "code\modules\tgui\states\self.dm" +#include "code\modules\tgui\states\zlevel.dm" +#include "code\modules\tooltip\tooltip.dm" +#include "code\modules\unit_tests\_unit_tests.dm" +#include "code\modules\uplink\uplink_devices.dm" +#include "code\modules\uplink\uplink_items.dm" +#include "code\modules\uplink\uplink_purchase_log.dm" +#include "code\modules\vehicles\_vehicle.dm" +#include "code\modules\vehicles\atv.dm" +#include "code\modules\vehicles\bicycle.dm" +#include "code\modules\vehicles\lavaboat.dm" +#include "code\modules\vehicles\motorized_wheelchair.dm" +#include "code\modules\vehicles\pimpin_ride.dm" +#include "code\modules\vehicles\ridden.dm" +#include "code\modules\vehicles\scooter.dm" +#include "code\modules\vehicles\sealed.dm" +#include "code\modules\vehicles\secway.dm" +#include "code\modules\vehicles\speedbike.dm" +#include "code\modules\vehicles\vehicle_actions.dm" +#include "code\modules\vehicles\vehicle_key.dm" +#include "code\modules\vehicles\wheelchair.dm" +#include "code\modules\vehicles\cars\car.dm" +#include "code\modules\vehicles\cars\clowncar.dm" +#include "code\modules\vending\_vending.dm" +#include "code\modules\vending\assist.dm" +#include "code\modules\vending\autodrobe.dm" +#include "code\modules\vending\boozeomat.dm" +#include "code\modules\vending\cartridge.dm" +#include "code\modules\vending\cigarette.dm" +#include "code\modules\vending\clothesmate.dm" +#include "code\modules\vending\coffee.dm" +#include "code\modules\vending\cola.dm" +#include "code\modules\vending\drinnerware.dm" +#include "code\modules\vending\engineering.dm" +#include "code\modules\vending\engivend.dm" +#include "code\modules\vending\games.dm" +#include "code\modules\vending\liberation.dm" +#include "code\modules\vending\liberation_toy.dm" +#include "code\modules\vending\magivend.dm" +#include "code\modules\vending\medical.dm" +#include "code\modules\vending\medical_wall.dm" +#include "code\modules\vending\megaseed.dm" +#include "code\modules\vending\nutrimax.dm" +#include "code\modules\vending\plasmaresearch.dm" +#include "code\modules\vending\robotics.dm" +#include "code\modules\vending\security.dm" +#include "code\modules\vending\snack.dm" +#include "code\modules\vending\sovietsoda.dm" +#include "code\modules\vending\sustenance.dm" +#include "code\modules\vending\toys.dm" +#include "code\modules\vending\wardrobes.dm" +#include "code\modules\vending\youtool.dm" +#include "code\modules\VR\vr_human.dm" +#include "code\modules\VR\vr_sleeper.dm" +#include "code\modules\zombie\items.dm" +#include "code\modules\zombie\organs.dm" +#include "hyperstation\code\datums\elements\holder_micro.dm" +#include "hyperstation\code\datums\mood_events\events.dm" +#include "hyperstation\code\datums\ruins\lavaland.dm" +#include "hyperstation\code\datums\traits\good.dm" +#include "hyperstation\code\datums\traits\neutral.dm" +#include "hyperstation\code\game\objects\structures\ghost_role_spawners.dm" +#include "hyperstation\code\gamemode\traitor_lewd.dm" +#include "hyperstation\code\gamemode\traitor_thief.dm" +#include "hyperstation\code\gamemode\werewolf\werewolf.dm" +#include "hyperstation\code\mobs\carrion.dm" +#include "hyperstation\code\mobs\hugbot.dm" +#include "hyperstation\code\mobs\mimic.dm" +#include "hyperstation\code\mobs\werewolf.dm" +#include "hyperstation\code\modules\traits.dm" +#include "hyperstation\code\modules\antagonists\werewolf\werewolf.dm" +#include "hyperstation\code\modules\arousal\arousalhud.dm" +#include "hyperstation\code\modules\client\loadout\glasses.dm" +#include "hyperstation\code\modules\client\loadout\tablet.dm" +#include "hyperstation\code\modules\clothing\head.dm" +#include "hyperstation\code\modules\clothing\glasses\polychromic_glasses.dm" +#include "hyperstation\code\modules\clothing\spacesuits\hardsuit.dm" +#include "hyperstation\code\modules\clothing\suits\misc.dm" +#include "hyperstation\code\modules\crafting\bounties.dm" +#include "hyperstation\code\modules\crafting\recipes.dm" +#include "hyperstation\code\modules\integrated_electronics\input.dm" +#include "hyperstation\code\modules\mob\mob_helpers.dm" +#include "hyperstation\code\modules\patreon\patreon.dm" +#include "hyperstation\code\modules\resize\resizing.dm" +#include "hyperstation\code\modules\resize\sizechems.dm" +#include "hyperstation\code\modules\resize\sizegun.dm" +#include "hyperstation\code\obj\bluespace sewing kit.dm" +#include "hyperstation\code\obj\condom.dm" +#include "hyperstation\code\obj\decal.dm" +#include "hyperstation\code\obj\fluff.dm" +#include "hyperstation\code\obj\kinkyclothes.dm" +#include "hyperstation\code\obj\leash.dm" +#include "hyperstation\code\obj\lunaritems.dm" +#include "hyperstation\code\obj\milking machine.dm" +#include "hyperstation\code\obj\plushes.dm" +#include "hyperstation\code\obj\pregnancytester.dm" +#include "hyperstation\code\obj\rewards.dm" +#include "hyperstation\code\obj\rope.dm" +#include "hyperstation\code\obj\sounding.dm" +#include "interface\interface.dm" +#include "interface\menu.dm" +#include "interface\stylesheet.dm" +#include "interface\skin.dmf" +#include "modular_citadel\code\init.dm" +#include "modular_citadel\code\__HELPERS\list2list.dm" +#include "modular_citadel\code\__HELPERS\lists.dm" +#include "modular_citadel\code\__HELPERS\mobs.dm" +#include "modular_citadel\code\_globalvars\lists\mobs.dm" +#include "modular_citadel\code\_onclick\click.dm" +#include "modular_citadel\code\_onclick\item_attack.dm" +#include "modular_citadel\code\_onclick\other_mobs.dm" +#include "modular_citadel\code\_onclick\hud\screen_objects.dm" +#include "modular_citadel\code\_onclick\hud\sprint.dm" +#include "modular_citadel\code\_onclick\hud\stamina.dm" +#include "modular_citadel\code\controllers\configuration\entries\general.dm" +#include "modular_citadel\code\controllers\subsystem\job.dm" +#include "modular_citadel\code\datums\components\material_container.dm" +#include "modular_citadel\code\datums\components\phantomthief.dm" +#include "modular_citadel\code\datums\components\souldeath.dm" +#include "modular_citadel\code\datums\mood_events\chem_events.dm" +#include "modular_citadel\code\datums\mood_events\generic_negative_events.dm" +#include "modular_citadel\code\datums\mood_events\generic_positive_events.dm" +#include "modular_citadel\code\datums\mood_events\moodular.dm" +#include "modular_citadel\code\datums\mutations\hulk.dm" +#include "modular_citadel\code\datums\status_effects\chems.dm" +#include "modular_citadel\code\datums\status_effects\debuffs.dm" +#include "modular_citadel\code\datums\traits\negative.dm" +#include "modular_citadel\code\datums\traits\neutral.dm" +#include "modular_citadel\code\datums\wires\airlock.dm" +#include "modular_citadel\code\datums\wires\autoylathe.dm" +#include "modular_citadel\code\game\area\cit_areas.dm" +#include "modular_citadel\code\game\gamemodes\gangs\dominator.dm" +#include "modular_citadel\code\game\gamemodes\gangs\dominator_countdown.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gang.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gang_datums.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gang_decals.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gang_hud.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gang_items.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gang_pen.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gangs.dm" +#include "modular_citadel\code\game\gamemodes\gangs\gangtool.dm" +#include "modular_citadel\code\game\gamemodes\gangs\implant_gang.dm" +#include "modular_citadel\code\game\gamemodes\miniantags\bot_swarm\swarmer_event.dm" +#include "modular_citadel\code\game\gamemodes\revolution\revolution.dm" +#include "modular_citadel\code\game\machinery\cryopod.dm" +#include "modular_citadel\code\game\machinery\displaycases.dm" +#include "modular_citadel\code\game\machinery\Sleeper.dm" +#include "modular_citadel\code\game\machinery\toylathe.dm" +#include "modular_citadel\code\game\machinery\vending.dm" +#include "modular_citadel\code\game\machinery\wishgranter.dm" +#include "modular_citadel\code\game\machinery\doors\airlock.dm" +#include "modular_citadel\code\game\machinery\doors\airlock_types.dm" +#include "modular_citadel\code\game\objects\cit_screenshake.dm" +#include "modular_citadel\code\game\objects\items.dm" +#include "modular_citadel\code\game\objects\tools.dm" +#include "modular_citadel\code\game\objects\effects\spawner\spawners.dm" +#include "modular_citadel\code\game\objects\effects\temporary_visuals\souldeath.dm" +#include "modular_citadel\code\game\objects\effects\temporary_visuals\projectiles\impact.dm" +#include "modular_citadel\code\game\objects\effects\temporary_visuals\projectiles\muzzle.dm" +#include "modular_citadel\code\game\objects\effects\temporary_visuals\projectiles\tracer.dm" +#include "modular_citadel\code\game\objects\items\balls.dm" +#include "modular_citadel\code\game\objects\items\boombox.dm" +#include "modular_citadel\code\game\objects\items\holy_weapons.dm" +#include "modular_citadel\code\game\objects\items\honk.dm" +#include "modular_citadel\code\game\objects\items\stunsword.dm" +#include "modular_citadel\code\game\objects\items\vending_items.dm" +#include "modular_citadel\code\game\objects\items\circuitboards\machine_circuitboards.dm" +#include "modular_citadel\code\game\objects\items\devices\aicard.dm" +#include "modular_citadel\code\game\objects\items\devices\radio\encryptionkey.dm" +#include "modular_citadel\code\game\objects\items\devices\radio\headset.dm" +#include "modular_citadel\code\game\objects\items\devices\radio\shockcollar.dm" +#include "modular_citadel\code\game\objects\items\melee\eutactic_blades.dm" +#include "modular_citadel\code\game\objects\items\melee\misc.dm" +#include "modular_citadel\code\game\objects\items\robot\robot_upgrades.dm" +#include "modular_citadel\code\game\objects\items\storage\firstaid.dm" +#include "modular_citadel\code\game\objects\structures\tables_racks.dm" +#include "modular_citadel\code\game\objects\structures\beds_chairs\chair.dm" +#include "modular_citadel\code\game\objects\structures\beds_chairs\sofa.dm" +#include "modular_citadel\code\game\objects\structures\crates_lockers\closets\fitness.dm" +#include "modular_citadel\code\game\objects\structures\crates_lockers\closets\wardrobe.dm" +#include "modular_citadel\code\game\objects\structures\crates_lockers\closets\secure\citadel_lockers.dm" +#include "modular_citadel\code\game\turfs\cit_turfs.dm" +#include "modular_citadel\code\modules\admin\chat_commands.dm" +#include "modular_citadel\code\modules\admin\holder2.dm" +#include "modular_citadel\code\modules\admin\secrets.dm" +#include "modular_citadel\code\modules\arousal\arousal.dm" +#include "modular_citadel\code\modules\arousal\organs\breasts.dm" +#include "modular_citadel\code\modules\arousal\organs\eggsack.dm" +#include "modular_citadel\code\modules\arousal\organs\genitals.dm" +#include "modular_citadel\code\modules\arousal\organs\genitals_sprite_accessories.dm" +#include "modular_citadel\code\modules\arousal\organs\ovipositor.dm" +#include "modular_citadel\code\modules\arousal\organs\penis.dm" +#include "modular_citadel\code\modules\arousal\organs\testicles.dm" +#include "modular_citadel\code\modules\arousal\organs\vagina.dm" +#include "modular_citadel\code\modules\arousal\organs\womb.dm" +#include "modular_citadel\code\modules\arousal\toys\dildos.dm" +#include "modular_citadel\code\modules\awaymissions\citadel_ghostrole_spawners.dm" +#include "modular_citadel\code\modules\cargo\console.dm" +#include "modular_citadel\code\modules\client\client_defines.dm" +#include "modular_citadel\code\modules\client\client_procs.dm" +#include "modular_citadel\code\modules\client\preferences.dm" +#include "modular_citadel\code\modules\client\preferences_savefile.dm" +#include "modular_citadel\code\modules\client\preferences_toggles.dm" +#include "modular_citadel\code\modules\client\loadout\__donator.dm" +#include "modular_citadel\code\modules\client\loadout\_medical.dm" +#include "modular_citadel\code\modules\client\loadout\_security.dm" +#include "modular_citadel\code\modules\client\loadout\_service.dm" +#include "modular_citadel\code\modules\client\loadout\backpack.dm" +#include "modular_citadel\code\modules\client\loadout\glasses.dm" +#include "modular_citadel\code\modules\client\loadout\gloves.dm" +#include "modular_citadel\code\modules\client\loadout\hands.dm" +#include "modular_citadel\code\modules\client\loadout\head.dm" +#include "modular_citadel\code\modules\client\loadout\loadout.dm" +#include "modular_citadel\code\modules\client\loadout\mask.dm" +#include "modular_citadel\code\modules\client\loadout\neck.dm" +#include "modular_citadel\code\modules\client\loadout\shoes.dm" +#include "modular_citadel\code\modules\client\loadout\suit.dm" +#include "modular_citadel\code\modules\client\loadout\uniform.dm" +#include "modular_citadel\code\modules\client\verbs\who.dm" +#include "modular_citadel\code\modules\clothing\clothing.dm" +#include "modular_citadel\code\modules\clothing\neck.dm" +#include "modular_citadel\code\modules\clothing\glasses\phantomthief.dm" +#include "modular_citadel\code\modules\clothing\head\head.dm" +#include "modular_citadel\code\modules\clothing\spacesuits\flightsuit.dm" +#include "modular_citadel\code\modules\clothing\suits\polychromic_cloaks.dm" +#include "modular_citadel\code\modules\clothing\suits\polychromic_suit.dm" +#include "modular_citadel\code\modules\clothing\suits\suits.dm" +#include "modular_citadel\code\modules\clothing\under\polychromic_clothes.dm" +#include "modular_citadel\code\modules\clothing\under\trek_under.dm" +#include "modular_citadel\code\modules\clothing\under\turtlenecks.dm" +#include "modular_citadel\code\modules\clothing\under\under.dm" +#include "modular_citadel\code\modules\crafting\recipes.dm" +#include "modular_citadel\code\modules\custom_loadout\custom_items.dm" +#include "modular_citadel\code\modules\custom_loadout\load_to_mob.dm" +#include "modular_citadel\code\modules\custom_loadout\read_from_file.dm" +#include "modular_citadel\code\modules\events\blob.dm" +#include "modular_citadel\code\modules\events\wizard\magicarp.dm" +#include "modular_citadel\code\modules\integrated_electronics\subtypes\manipulation.dm" +#include "modular_citadel\code\modules\jobs\dresscode_values.dm" +#include "modular_citadel\code\modules\mentor\follow.dm" +#include "modular_citadel\code\modules\mentor\mentor.dm" +#include "modular_citadel\code\modules\mentor\mentor_memo.dm" +#include "modular_citadel\code\modules\mentor\mentor_verbs.dm" +#include "modular_citadel\code\modules\mentor\mentorhelp.dm" +#include "modular_citadel\code\modules\mentor\mentorpm.dm" +#include "modular_citadel\code\modules\mentor\mentorsay.dm" +#include "modular_citadel\code\modules\mining\mining_ruins.dm" +#include "modular_citadel\code\modules\mob\cit_emotes.dm" +#include "modular_citadel\code\modules\mob\dead\new_player\sprite_accessories.dm" +#include "modular_citadel\code\modules\mob\living\damage_procs.dm" +#include "modular_citadel\code\modules\mob\living\living.dm" +#include "modular_citadel\code\modules\mob\living\carbon\carbon.dm" +#include "modular_citadel\code\modules\mob\living\carbon\damage_procs.dm" +#include "modular_citadel\code\modules\mob\living\carbon\life.dm" +#include "modular_citadel\code\modules\mob\living\carbon\reindex_screams.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\human.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\human_defense.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\human_movement.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\life.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\species.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\furrypeople.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\ipc.dm" +#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\jellypeople.dm" +#include "modular_citadel\code\modules\mob\living\silicon\robot\dogborg_equipment.dm" +#include "modular_citadel\code\modules\mob\living\silicon\robot\robot.dm" +#include "modular_citadel\code\modules\mob\living\silicon\robot\robot_modules.dm" +#include "modular_citadel\code\modules\mob\living\silicon\robot\robot_movement.dm" +#include "modular_citadel\code\modules\mob\living\simple_animal\banana_spider.dm" +#include "modular_citadel\code\modules\mob\living\simple_animal\kiwi.dm" +#include "modular_citadel\code\modules\mob\living\simple_animal\simplemob_vore_values.dm" +#include "modular_citadel\code\modules\power\lighting.dm" +#include "modular_citadel\code\modules\projectiles\gun.dm" +#include "modular_citadel\code\modules\projectiles\ammunition\caseless.dm" +#include "modular_citadel\code\modules\projectiles\ammunition\ballistic\smg\smg.dm" +#include "modular_citadel\code\modules\projectiles\boxes_magazines\ammo_boxes.dm" +#include "modular_citadel\code\modules\projectiles\boxes_magazines\external\pistol.dm" +#include "modular_citadel\code\modules\projectiles\boxes_magazines\external\smg\smg.dm" +#include "modular_citadel\code\modules\projectiles\bullets\bullets\smg.dm" +#include "modular_citadel\code\modules\projectiles\guns\pumpenergy.dm" +#include "modular_citadel\code\modules\projectiles\guns\toys.dm" +#include "modular_citadel\code\modules\projectiles\guns\ballistic\handguns.dm" +#include "modular_citadel\code\modules\projectiles\guns\ballistic\magweapon.dm" +#include "modular_citadel\code\modules\projectiles\guns\ballistic\magweapon_energy.dm" +#include "modular_citadel\code\modules\projectiles\guns\ballistic\revolver.dm" +#include "modular_citadel\code\modules\projectiles\guns\ballistic\rifles.dm" +#include "modular_citadel\code\modules\projectiles\guns\ballistic\spinfusor.dm" +#include "modular_citadel\code\modules\projectiles\guns\energy\energy_gun.dm" +#include "modular_citadel\code\modules\projectiles\guns\energy\laser.dm" +#include "modular_citadel\code\modules\projectiles\projectiles\reusable.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\astrogen.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\eigentstasium.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\enlargement.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\fermi_reagents.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\healing.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\MKUltra.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\other_reagents.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\SDGF.dm" +#include "modular_citadel\code\modules\reagents\chemistry\recipes\fermi.dm" +#include "modular_citadel\code\modules\reagents\objects\clothes.dm" +#include "modular_citadel\code\modules\reagents\objects\items.dm" +#include "modular_citadel\code\modules\reagents\reagents\cit_reagents.dm" +#include "modular_citadel\code\modules\recycling\disposal\bin.dm" +#include "modular_citadel\code\modules\research\designs\autoylathe_designs.dm" +#include "modular_citadel\code\modules\research\designs\machine_designs.dm" +#include "modular_citadel\code\modules\research\designs\xenobio_designs.dm" +#include "modular_citadel\code\modules\research\techweb\_techweb.dm" +#include "modular_citadel\code\modules\research\xenobiology\xenobio_camera.dm" +#include "modular_citadel\code\modules\vehicles\secway.dm" +#include "modular_citadel\code\modules\vore\hook-defs_vr.dm" +#include "modular_citadel\code\modules\vore\persistence.dm" +#include "modular_citadel\code\modules\vore\trycatch_vr.dm" +#include "modular_citadel\code\modules\vore\eating\belly_dat_vr.dm" +#include "modular_citadel\code\modules\vore\eating\belly_obj_vr.dm" +#include "modular_citadel\code\modules\vore\eating\bellymodes_vr.dm" +#include "modular_citadel\code\modules\vore\eating\digest_act_vr.dm" +#include "modular_citadel\code\modules\vore\eating\living_vr.dm" +#include "modular_citadel\code\modules\vore\eating\vore_vr.dm" +#include "modular_citadel\code\modules\vore\eating\voreitems.dm" +#include "modular_citadel\code\modules\vore\eating\vorepanel_vr.dm" +#include "modular_citadel\interface\skin.dmf" +#include "yogstation\code\modules\power\energyharvester.dm" +// END_INCLUDE From 66a9ce77fb4b777477fbb54a3b1e1f1e284b95d2 Mon Sep 17 00:00:00 2001 From: Putnam3145 Date: Fri, 29 Nov 2019 06:20:18 -0800 Subject: [PATCH 18/21] Merge pull request #9964 from GrayRachnid/fixmyblunder Fixes Codewords not changing color and MkUltra enthrall not having the proper hearing_args --- code/modules/antagonists/traitor/datum_traitor.dm | 3 ++- modular_citadel/code/datums/status_effects/chems.dm | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index ba762bea..fde46cba 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -23,6 +23,7 @@ owner.special_role = special_role if(give_objectives) forge_traitor_objectives() + RegisterSignal(owner.current, COMSIG_MOVABLE_HEAR, .proc/handle_hearing) finalize_traitor() ..() @@ -48,6 +49,7 @@ A.verbs -= /mob/living/silicon/ai/proc/choose_modules A.malf_picker.remove_malf_verbs(A) qdel(A.malf_picker) + UnregisterSignal(owner.current, COMSIG_MOVABLE_HEAR, .proc/handle_hearing) /datum/antagonist/traitor/proc/handle_hearing(datum/source, list/hearing_args) var/message = hearing_args[HEARING_MESSAGE] @@ -59,7 +61,6 @@ if(!silent && owner.current) to_chat(owner.current," You are no longer the [special_role]! ") owner.special_role = null - ..() /datum/antagonist/traitor/proc/add_objective(var/datum/objective/O) owner.objectives += O diff --git a/modular_citadel/code/datums/status_effects/chems.dm b/modular_citadel/code/datums/status_effects/chems.dm index 4ee7ff55..9441668f 100644 --- a/modular_citadel/code/datums/status_effects/chems.dm +++ b/modular_citadel/code/datums/status_effects/chems.dm @@ -656,18 +656,18 @@ UnregisterSignal(owner, COMSIG_GLOB_LIVING_SAY_SPECIAL) -/datum/status_effect/chem/enthrall/proc/owner_hear(var/hearer, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) +/datum/status_effect/chem/enthrall/proc/owner_hear(datum/source, list/hearing_args) if(owner.client?.prefs.lewdchem == FALSE) return if (cTriggered > 0) return var/mob/living/carbon/C = owner - raw_message = lowertext(raw_message) + var/raw_message = lowertext(hearing_args[HEARING_RAW_MESSAGE]) for (var/trigger in customTriggers) var/cached_trigger = lowertext(trigger) if (findtext(raw_message, cached_trigger))//if trigger1 is the message cTriggered = 5 //Stops triggerparties and as a result, stops servercrashes. - log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been triggered with [cached_trigger] from [speaker] saying: \"[message]\". (their master being [master] ckey: [enthrallID].)") + log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been triggered with [cached_trigger] from [hearing_args[HEARING_SPEAKER]] saying: \"[hearing_args[HEARING_MESSAGE]]\". (their master being [master] ckey: [enthrallID].)") //Speak (Forces player to talk) if (lowertext(customTriggers[trigger][1]) == "speak")//trigger2 @@ -705,7 +705,7 @@ C.adjustArousalLoss(10) to_chat(C, "You feel a surge of arousal!") else - C.throw_at(get_step_towards(speaker,C), 3, 1) //cut this if it's too hard to get working + C.throw_at(get_step_towards(hearing_args[HEARING_SPEAKER],C), 3, 1) //cut this if it's too hard to get working //kneel (knockdown) else if (lowertext(customTriggers[trigger]) == "kneel")//as close to kneeling as you can get, I suppose. From f2d51707fb71579fdbdfead7de1346f332632a45 Mon Sep 17 00:00:00 2001 From: Archie Date: Wed, 6 Jan 2021 22:56:41 -0300 Subject: [PATCH 19/21] tones down things that could nuke the codebase --- code/datums/mutations/speech.dm | 2 +- .../mob/dead/new_player/sprite_accessories/hair_head.dm | 2 +- html/archivedchangelog.html | 5 ++--- strings/traumas.json | 6 +++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm index 56ce3533..c7eaefcc 100644 --- a/code/datums/mutations/speech.dm +++ b/code/datums/mutations/speech.dm @@ -263,7 +263,7 @@ message = replacetext(message," thank you "," thank you, thank you very much ") message = replacetext(message," what are you "," whatcha ") message = replacetext(message," yes ",pick(" sure", "yea ")) - message = replacetext(message," faggot "," square ") + message = replacetext(message," fucker "," square ") message = replacetext(message," muh valids "," getting my kicks ") speech_args[SPEECH_MESSAGE] = trim(message) diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm index f9a22678..cea9fbc0 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm @@ -185,7 +185,7 @@ name = "Flat Top (Big)" icon_state = "hair_bigflattop" -/datum/sprite_accessory/hair/fag +/datum/sprite_accessory/hair/flow name = "Flow Hair" icon_state = "hair_f" diff --git a/html/archivedchangelog.html b/html/archivedchangelog.html index 6b0fb6c6..6c139e77 100644 --- a/html/archivedchangelog.html +++ b/html/archivedchangelog.html @@ -7315,7 +7315,6 @@ resistance (only slight don't run into a fire).
  • HoS, Warden and Security Officer starting equipment changed.
  • New crate, which contains armored vests and helmets. Requires security access, costs 20.
  • Miner lockers now contain meson scanners and mining jumpsuits.
  • -
  • Food crate now contains milk, instead of faggots. Lightbulb crates cost reduced to 5. Riot crates cost reduced to 20. Emergency crate contains 2 med bots instead of floor bots. Hydroponics crate no longer contains weed spray, pest spray. It's latex gloves were replaced with leather ones and an apron.
  • Added chef's apron (can hold a kitchen knife) and a new service borg sprite.
  • Autolathe can now construct kitchen knives.
  • Biosuit and syndicate space suits can now fit into backpacks.
  • @@ -8693,7 +8692,7 @@ resistance (only slight don't run into a fire).
  • BACKUP BACKUP TELL ME WHAT YOU GONNA DO NOW Changed the monkey name code. Re-did my antimatter engine code so it actually puts out power now
  • dumb as fuck change, whoever did that, it already spawn ()&#39;s inside the proc - code, whoever did that, you are a faggot and should read code before you modify + code, whoever did that, you are an idiot and should read code before you modify it
  • Fixed a bug that gave everyone modify ticker variables you silly sausage.
  • Sorted the AIs track list.
  • @@ -9048,4 +9047,4 @@ resistance (only slight don't run into a fire).
  • Seperated male and female names.
  • Females have pink underwear.
  • Guests can no longer save/load profiles, as this just created useless profiles that weren't used again.
  • - \ No newline at end of file + diff --git a/strings/traumas.json b/strings/traumas.json index 45e31f23..b0715c00 100644 --- a/strings/traumas.json +++ b/strings/traumas.json @@ -3,7 +3,7 @@ "@pick(semicolon)IM A PONY NEEEEEEIIIIIIIIIGH", "without oxigen blob don't evoluate?", "@pick(semicolon)CAPTAINS A COMDOM", - "@pick(semicolon)@pick(faggot_traitor) @pick(george) @pick(mellens) is grifing me HALP!!!", + "@pick(semicolon)@pick(fucking_traitor) @pick(george) @pick(mellens) is grifing me HALP!!!", "can u give me @pick(mutations)?", "THe saiyans screwed", "Bi is THE BEST OF BOTH WORLDS>", @@ -161,9 +161,9 @@ "mwrlins" ], - "faggot_traitor": [ + "fucking_traitor": [ "", - "that faggot traitor" + "that fucking traitor" ], "random_gibberish": [ From f1d2abe08144668c1e532202a6cfb9349283635f Mon Sep 17 00:00:00 2001 From: Archie Date: Thu, 7 Jan 2021 15:19:53 -0300 Subject: [PATCH 20/21] Bucklefix --- code/_onclick/cyborg.dm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index b0191d89..a2d566d5 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -55,6 +55,17 @@ var/obj/item/W = get_active_held_item() + if(!W && A.Adjacent(src) && (isobj(A) || ismob(A))) + var/atom/movable/C = A + if(C.can_buckle && C.has_buckled_mobs()) + if(C.buckled_mobs.len > 1) + var/unbuckled = input(src, "Who do you wish to unbuckle?","Unbuckle Who?") as null|mob in C.buckled_mobs + if(C.user_unbuckle_mob(unbuckled,src)) + return + else + if(C.user_unbuckle_mob(C.buckled_mobs[1],src)) + return + if(!W && get_dist(src,A) <= interaction_range) A.attack_robot(src) return From 6bb44989190bb07f73c34bc864f7ec4b084d760e Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Sun, 3 Nov 2019 06:20:30 -0700 Subject: [PATCH 21/21] Merge pull request #9658 from Ghommie/Ghommie-cit291 Fixing dogborg sleepers runtimes, how atrocious... --- .../objects/items/devices/dogborg_sleeper.dm | 227 ++++++++---------- 1 file changed, 103 insertions(+), 124 deletions(-) diff --git a/code/game/objects/items/devices/dogborg_sleeper.dm b/code/game/objects/items/devices/dogborg_sleeper.dm index 6db2e9af..6623a40b 100644 --- a/code/game/objects/items/devices/dogborg_sleeper.dm +++ b/code/game/objects/items/devices/dogborg_sleeper.dm @@ -6,8 +6,7 @@ icon = 'icons/mob/dogborg.dmi' icon_state = "sleeper" w_class = WEIGHT_CLASS_TINY - var/mob/living/carbon/patient = null - var/mob/living/silicon/robot/hound = null + var/mob/living/carbon/patient var/inject_amount = 10 var/min_health = -100 var/cleaning = FALSE @@ -62,8 +61,18 @@ /obj/item/dogborg/sleeper/Exit(atom/movable/O) return 0 +/obj/item/dogborg/sleeper/proc/get_host() + if(!loc) + return + if(iscyborg(loc)) + return loc + else if(iscyborg(loc.loc)) + return loc.loc //cursed cyborg code + /obj/item/dogborg/sleeper/afterattack(mob/living/carbon/target, mob/living/silicon/user, proximity) - hound = loc + var/mob/living/silicon/robot/hound = get_host() + if(!hound) + return if(!proximity) return if(!iscarbon(target)) @@ -83,7 +92,8 @@ if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. return //If they moved away, you can't eat them. - if(patient) return //If you try to eat two people at once, you can only eat one. + if(patient) + return //If you try to eat two people at once, you can only eat one. else //If you don't have someone in you, proceed. if(!isjellyperson(target) && ("toxin" in injection_chems)) @@ -95,14 +105,17 @@ target.forceMove(src) target.reset_perspective(src) target.ExtinguishMob() //The tongue already puts out fire stacks but being put into the sleeper shouldn't allow you to keep burning. - update_gut() + update_gut(hound) user.visible_message("[voracious ? "[hound]'s [src.name] lights up and expands as [target] slips inside into their [src.name]." : "[hound]'s sleeper indicator lights up as [target] is scooped up into [hound.p_their()] [src]."]", \ "Your [voracious ? "[src.name] lights up as [target] slips into" : "sleeper indicator light shines brightly as [target] is scooped inside"] your [src]. Life support functions engaged.") message_admins("[key_name(hound)] has sleeper'd [key_name(patient)] as a dogborg. [ADMIN_JMP(src)]") playsound(hound, 'sound/effects/bin_close.ogg', 100, 1) /obj/item/dogborg/sleeper/container_resist(mob/living/user) - hound = loc + var/mob/living/silicon/robot/hound = get_host() + if(!hound) + go_out(user) + return user.changeNext_move(CLICK_CD_BREAKOUT) user.last_special = world.time + CLICK_CD_BREAKOUT if(user.a_intent == INTENT_HELP) @@ -114,55 +127,41 @@ "[voracious ? "You start struggling inside of [src]'s tight, flexible confines," : "You start pounding against the metallic walls of [src],"] trying to trigger the release... (this will take about [DisplayTimeText(breakout_time)].)", \ "You hear a [voracious ? "couple of thumps" : "loud banging noise"] coming from within [hound].") if(do_after(user, breakout_time, target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return user.visible_message("[user] successfully broke out of [hound.name]!", \ "You successfully break out of [hound.name]!") - go_out() + go_out(user, hound) -/obj/item/dogborg/sleeper/proc/go_out(var/target) - hound = loc - hound.setClickCooldown(50) - var/voracious = TRUE - if(!hound.client || !(hound.client.prefs.cit_toggles & MEDIHOUND_SLEEPER)) - voracious = FALSE - else - for(var/mob/M in contents) - if(!M.client || !(M.client.prefs.cit_toggles & MEDIHOUND_SLEEPER)) - voracious = FALSE - if(length(contents) > 0) - hound.visible_message("[voracious ? "[hound] empties out [hound.p_their()] contents via [hound.p_their()] release port." : "[hound]'s underside slides open with an audible clunk before [hound.p_their()] [src] flips over, carelessly dumping its contents onto the ground below [hound.p_them()] before closing right back up again."]", \ - "[voracious ? "You empty your contents via your release port." : "You open your sleeper hatch, quickly releasing all of the contents within before closing it again."]") - if(target) - if(iscarbon(target)) - var/mob/living/carbon/person = target - person.forceMove(get_turf(src)) - person.reset_perspective() - else - var/obj/T = target - T.loc = hound.loc +/obj/item/dogborg/sleeper/proc/go_out(atom/movable/target, mob/living/silicon/robot/hound) + var/voracious = hound ? TRUE : FALSE + var/list/targets = target && hound ? list(target) : contents + if(hound) + hound.setClickCooldown(50) + if(!hound.client || !(hound.client.prefs.cit_toggles & MEDIHOUND_SLEEPER)) + voracious = FALSE else - for(var/C in contents) - if(iscarbon(C)) - var/mob/living/carbon/person = C - person.forceMove(get_turf(src)) - person.reset_perspective() - else - var/obj/T = C - T.loc = hound.loc - items_preserved.Cut() - update_gut() - cleaning = FALSE + for(var/mob/M in targets) + if(!M.client || !(M.client.prefs.cit_toggles & MEDIHOUND_SLEEPER)) + voracious = FALSE + if(length(targets)) + if(hound) + hound.visible_message("[voracious ? "[hound] empties out [hound.p_their()] contents via [hound.p_their()] release port." : "[hound]'s underside slides open with an audible clunk before [hound.p_their()] [src] flips over, carelessly dumping its contents onto the ground below [hound.p_them()] before closing right back up again."]", \ + "[voracious ? "You empty your contents via your release port." : "You open your sleeper hatch, quickly releasing all of the contents within before closing it again."]") + for(var/a in contents) + var/atom/movable/AM = a + AM.forceMove(get_turf(src)) + if(ismob(AM)) + var/mob/M = AM + M.reset_perspective() playsound(loc, voracious ? 'sound/effects/splat.ogg' : 'sound/effects/bin_close.ogg', 50, 1) - - else //You clicked eject with nothing in you, let's just reset stuff to be sure. - items_preserved.Cut() - cleaning = FALSE - update_gut() + items_preserved.Cut() + cleaning = FALSE + if(hound) + update_gut(hound) /obj/item/dogborg/sleeper/attack_self(mob/user) - if(..()) + . = ..() + if(. || !iscyborg(user)) return ui_interact(user) @@ -220,30 +219,31 @@ return data /obj/item/dogborg/sleeper/ui_act(action, params) - if(..()) + . = ..() + if(. || !iscyborg(usr)) return switch(action) if("eject") - go_out() + go_out(null, usr) . = TRUE if("inject") var/chem = text2path(params["chem"]) if(!patient || !chem) return - inject_chem(chem) + inject_chem(chem, usr) . = TRUE if("cleaning") if(!contents) to_chat(src, "Your [src] is already cleaned.") return if(patient) - to_chat(patient, "[hound.name]'s [src] fills with caustic enzymes around you!") + to_chat(patient, "[usr.name]'s [src] fills with caustic enzymes around you!") to_chat(src, "Cleaning process enabled.") - clean_cycle() + clean_cycle(usr) . = TRUE -/obj/item/dogborg/sleeper/proc/update_gut() +/obj/item/dogborg/sleeper/proc/update_gut(mob/living/silicon/robot/hound) //Well, we HAD one, what happened to them? var/prociconupdate = FALSE var/currentenvy = hound.sleeper_nv @@ -292,7 +292,7 @@ //Update icon and return new patient hound.update_icons() - return(C) + return //Cleaning looks better with red on, even with nobody in it if(cleaning && !patient) @@ -308,8 +308,10 @@ hound.update_icons() //Gurgleborg process -/obj/item/dogborg/sleeper/proc/clean_cycle() +/obj/item/dogborg/sleeper/proc/clean_cycle(mob/living/silicon/robot/hound) //Sanity + if(!hound) + return for(var/I in items_preserved) if(!(I in contents)) items_preserved -= I @@ -321,13 +323,12 @@ if(cleaning_cycles) cleaning_cycles-- cleaning = TRUE - for(var/mob/living/carbon/human/T in (touchable_items)) - if((T.status_flags & GODMODE) || !T.digestable) - items_preserved += T + for(var/mob/living/carbon/C in (touchable_items)) + if((C.status_flags & GODMODE) || !C.digestable) + items_preserved += C else - T.adjustBruteLoss(2) - T.adjustFireLoss(3) - update_gut() + C.adjustBruteLoss(2) + C.adjustFireLoss(3) if(contents) var/atom/target = pick(touchable_items) if(iscarbon(target)) //Handle the target being a mob @@ -360,7 +361,6 @@ if(!T.dropItemToGround(W)) qdel(W) qdel(T) - update_gut() //Handle the target being anything but a mob else if(isobj(target)) var/obj/T = target @@ -375,12 +375,10 @@ cleaning_cycles = initial(cleaning_cycles) cleaning = FALSE to_chat(hound, "Your [src] chimes it ends its self-cleaning cycle.")//Belly is entirely empty - update_gut() if(!length(contents)) to_chat(hound, "Your [src] is now clean. Ending self-cleaning cycle.") cleaning = FALSE - update_gut() //sound effects if(prob(50)) @@ -398,13 +396,17 @@ else if(H in contents) H.playsound_local(source, null, 65, falloff = 0, S = prey_digest) + update_gut(hound) + if(cleaning) - addtimer(CALLBACK(src, .proc/clean_cycle), 50) + addtimer(CALLBACK(src, .proc/clean_cycle, hound), 50) /obj/item/dogborg/sleeper/proc/CheckAccepted(obj/item/I) return is_type_in_typecache(I, important_items) -/obj/item/dogborg/sleeper/proc/inject_chem(chem) +/obj/item/dogborg/sleeper/proc/inject_chem(chem, mob/living/silicon/robot/hound) + if(!hound) + return if(hound.cell.charge <= 800) //This is so borgs don't kill themselves with it. Remember, 750 charge used every injection. to_chat(hound, "You don't have enough power to synthesize fluids.") return @@ -439,40 +441,27 @@ else . = ..() -/obj/item/dogborg/sleeper/K9/afterattack(var/atom/movable/target, mob/living/silicon/user, proximity) - hound = loc +/obj/item/dogborg/sleeper/K9/afterattack(mob/living/carbon/target, mob/living/silicon/user, proximity) + var/mob/living/silicon/robot/hound = get_host() + if(!hound || !istype(target) || !proximity || target.anchored) + return + if (!target.devourable) + to_chat(user, "The target registers an error code. Unable to insert into [src].") + return + if(target) + to_chat(user,"Your [src] is already occupied.") + return + if(target.buckled) + to_chat(user,"[target] is buckled and can not be put into your [src].") + return + user.visible_message("[hound.name] is ingesting [target] into their [src].", "You start ingesting [target] into your [src.name]...") + if(do_after(user, 30, target = target) && !patient && !target.buckled) + target.forceMove(src) + target.reset_perspective(src) + update_gut(hound) + user.visible_message("[hound.name]'s mobile brig clunks in series as [target] slips inside.", "Your mobile brig groans lightly as [target] slips inside.") + playsound(hound, 'sound/effects/bin_close.ogg', 80, 1) // Really don't need ERP sound effects for robots - if(!istype(target)) - return - if(!proximity) - return - if(target.anchored) - return - if(isobj(target)) - to_chat(user, "You are above putting such trash inside of yourself.") - return - if(iscarbon(target)) - var/mob/living/carbon/brigman = target - if (!brigman.devourable) - to_chat(user, "The target registers an error code. Unable to insert into [src].") - return - if(patient) - to_chat(user,"Your [src] is already occupied.") - return - if(brigman.buckled) - to_chat(user,"[brigman] is buckled and can not be put into your [src].") - return - user.visible_message("[hound.name] is ingesting [brigman] into their [src].", "You start ingesting [brigman] into your [src.name]...") - if(do_after(user, 30, target = brigman) && !patient && !brigman.buckled) - if(!in_range(src, brigman)) //Proximity is probably old news by now, do a new check. - return //If they moved away, you can't eat them. - brigman.forceMove(src) - brigman.reset_perspective(src) - update_gut() - user.visible_message("[hound.name]'s mobile brig clunks in series as [brigman] slips inside.", "Your mobile brig groans lightly as [brigman] slips inside.") - playsound(hound, 'sound/effects/bin_close.ogg', 80, 1) // Really don't need ERP sound effects for robots - return - return /obj/item/dogborg/sleeper/compactor //Janihound gut. name = "garbage processor" @@ -490,31 +479,25 @@ else . = ..() -/obj/item/dogborg/sleeper/compactor/afterattack(var/atom/movable/target, mob/living/silicon/user, proximity)//GARBO NOMS - hound = loc - var/obj/item/target_obj = target - if(!istype(target)) - return - if(!proximity) - return - if(target.anchored) +/obj/item/dogborg/sleeper/compactor/afterattack(atom/movable/target, mob/living/silicon/user, proximity)//GARBO NOMS + var/mob/living/silicon/robot/hound = get_host() + if(!hound || !istype(target) || !proximity || target.anchored) return if(length(contents) > (max_item_count - 1)) to_chat(user,"Your [src] is full. Eject or process contents to continue.") return - if(isobj(target)) - if(CheckAccepted(target)) - to_chat(user,"\The [target] registers an error code to your [src]") + if(isitem(target)) + var/obj/item/I = target + if(CheckAccepted(I)) + to_chat(user,"[I] registers an error code to your [src]") return - if(target_obj.w_class > WEIGHT_CLASS_NORMAL) - to_chat(user,"\The [target] is too large to fit into your [src]") + if(I.w_class > WEIGHT_CLASS_NORMAL) + to_chat(user,"[I] is too large to fit into your [src]") return - user.visible_message("[hound.name] is ingesting [target.name] into their [src.name].", "You start ingesting [target] into your [src.name]...") + user.visible_message("[hound.name] is ingesting [I] into their [src.name].", "You start ingesting [target] into your [src.name]...") if(do_after(user, 15, target = target) && length(contents) < max_item_count) - if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. - return //If they moved away, you can't eat them. This still applies to items, don't magically eat things I picked up already. - target.forceMove(src) - user.visible_message("[hound.name]'s garbage processor groans lightly as [target.name] slips inside.", "Your garbage compactor groans lightly as [target] slips inside.") + I.forceMove(src) + I.visible_message("[hound.name]'s garbage processor groans lightly as [I] slips inside.", "Your garbage compactor groans lightly as [I] slips inside.") playsound(hound, 'sound/machines/disposalflush.ogg', 50, 1) if(length(contents) > 11) //grow that tum after a certain junk amount hound.sleeper_r = 1 @@ -524,9 +507,9 @@ hound.update_icons() return - else if(iscarbon(target)) - var/mob/living/carbon/trashman = target - if (!trashman.devourable) + if(iscarbon(target) || issilicon(target)) + var/mob/living/trashman = target + if(!trashman.devourable) to_chat(user, "[target] registers an error code to your [src]") return if(patient) @@ -537,12 +520,8 @@ return user.visible_message("[hound.name] is ingesting [trashman] into their [src].", "You start ingesting [trashman] into your [src.name]...") if(do_after(user, 30, target = trashman) && !patient && !trashman.buckled && length(contents) < max_item_count) - if(!in_range(src, trashman)) //Proximity is probably old news by now, do a new check. - return //If they moved away, you can't eat them. trashman.forceMove(src) trashman.reset_perspective(src) update_gut() user.visible_message("[hound.name]'s garbage processor groans lightly as [trashman] slips inside.", "Your garbage compactor groans lightly as [trashman] slips inside.") playsound(hound, 'sound/effects/bin_close.ogg', 80, 1) - return - return