diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index a11cdb83f0c90..17e10754ea7c1 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -110,7 +110,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isflyperson(A) (is_species(A, /datum/species/fly)) #define isjellyperson(A) (is_species(A, /datum/species/jelly)) #define isslimeperson(A) (is_species(A, /datum/species/jelly/slime)) -#define iszombie(A) (is_species(A, /datum/species/zombie)) +#define iszombie(A) (is_species(A, /datum/species/zombie) || HAS_TRAIT(A, TRAIT_UNDEAD)) // SPLURT EDIT - Quirks #define isskeleton(A) (is_species(A, /datum/species/skeleton)) #define ismoth(A) (is_species(A, /datum/species/moth)) #define isfelinid(A) (is_species(A, /datum/species/human/felinid)) diff --git a/code/__DEFINES/~~~splurt_defines/dcs/signals_reagents.dm b/code/__DEFINES/~~~splurt_defines/dcs/signals_reagents.dm new file mode 100644 index 0000000000000..33debcc0549a8 --- /dev/null +++ b/code/__DEFINES/~~~splurt_defines/dcs/signals_reagents.dm @@ -0,0 +1,86 @@ +// Signals involving reagents with a mob + +// +// ADD SIGNALS +// + +/** + * Triggered when adding Cum or Femcum + * * Used by quirk: Concubus + * * Used by quirk: Dumb For Cum + */ +#define COMSIG_REAGENT_ADD_CUM "reagent_add_cum" + +/** + * Triggered when adding Breast Milk + * * Used by quirk: Concubus + */ +#define COMSIG_REAGENT_ADD_BREASTMILK "reagent_add_breastmilk" + +/** + * Triggered when adding Blood + * * Used by quirk: Bloodfledge + */ +#define COMSIG_REAGENT_ADD_BLOOD "reagent_add_blood" + +// +// METABOLIZE SIGNALS +// + +/** + * Triggered when metabolizing Nuka Cola + * * Used by quirk: Rad Fiend + */ +#define COMSIG_REAGENT_METABOLIZE_NUKACOLA "reagent_metabolize_nukacola" + +/** + * Triggered when metabolizing Holy Water + * * Used by quirk: Hallowed + */ +#define COMSIG_REAGENT_METABOLIZE_HOLYWATER "reagent_metabolize_holywater" + +/** + * Triggered when done metabolizing Holy Water + * * Used by quirk: Cursed Blood + */ +#define COMSIG_REAGENT_METABOLIZE_END_HOLYWATER "reagent_metabolize_end_holywater" + +// +// PROCESSING SIGNALS +// + +/** + * Triggered when processing Holy Water + * * Used by quirk: Bloodfledge + * * Used by quirk: Cursed Blood + * * Used by quirk: Hallowed + */ +#define COMSIG_REAGENT_PROCESS_HOLYWATER "reagent_process_holywater" + +/** + * Triggered when processing Hell Water + * * Used by quirk: Cursed Blood + */ +#define COMSIG_REAGENT_PROCESS_HELLWATER "reagent_process_hellwater" + +/** + * Triggered when processing Salt + * * Used by quirk: Sodium Sensitivity + */ +#define COMSIG_REAGENT_PROCESS_SALT "reagent_process_salt" + +// +// MOB EXPOSE SIGNALS +// + +/** + * Triggered when a mob is exposed to Salt + * * Used by quirk: Sodium Sensitivity + */ +#define COMSIG_REAGENT_EXPOSE_SALT "reagent_expose_salt" + +/** + * Triggered when a mob is exposed to Holy Water + * * Used by quirk: Sodium Sensitivity + */ +#define COMSIG_REAGENT_EXPOSE_HOLYWATER "reagent_expose_holywater" diff --git a/code/__DEFINES/~~~splurt_defines/spans.dm b/code/__DEFINES/~~~splurt_defines/spans.dm new file mode 100644 index 0000000000000..84cc87f427729 --- /dev/null +++ b/code/__DEFINES/~~~splurt_defines/spans.dm @@ -0,0 +1,8 @@ +// Entries here originally from Sandstorm Station 13 +#define span_lewd(str) ("" + str + "") +#define span_love(str) ("" + str + "") + +// Entries here from SPLURT +#define span_reallybig_hypnophrase(str) ("" + str + "") +#define span_big_warning(str) ("" + str + "") +#define span_umbra(str) ("" + str + "") diff --git a/code/__DEFINES/~~~splurt_defines/traits/declarations.dm b/code/__DEFINES/~~~splurt_defines/traits/declarations.dm index e69de29bb2d1d..895ed2a277d6e 100644 --- a/code/__DEFINES/~~~splurt_defines/traits/declarations.dm +++ b/code/__DEFINES/~~~splurt_defines/traits/declarations.dm @@ -0,0 +1,89 @@ +/// +/// Quirk traits +/// +/// Trait for quirk: Sodium Sensitivity +#define TRAIT_SALT_SENSITIVE "salt_sensitive" +/// Trait for quirk: Rad Fiend +#define TRAIT_RAD_FIEND "rad_fiend" +/// Trait for quirk: Hypnotic Gaze +#define TRAIT_HYPNOTIC_GAZE "hypnotic_gaze" +/// Trait for quirk: Genital Sniffer +#define TRAIT_GFLUID_DETECT "genital_fluid_detect" +/// Trait for quirk: Ashen Resistance +#define TRAIT_ASHRESISTANCE "ash_resistance" +/// Trait for quirk: Choke Slut +#define TRAIT_CHOKE_SLUT "choke_slut" +/// Trait for quirk: Bloodsucker Fledgling +#define TRAIT_BLOODFLEDGE "bloodfledge" +/// UNUSED - Legacy trait for quirk: Incubus +#define TRAIT_INCUBUS "incubus" +/// UNUSED - Legacy trait for quirk: Succubus +#define TRAIT_SUCCUBUS "succubus" +/// Trait for quirk: Silkspinner +#define TRAIT_ARACHNID "arachnid" +/// Trait for quirk: Flutter +#define TRAIT_FLUTTER "flutter" +/// Trait for quirk: Nudist +#define TRAIT_NUDIST "nudist" +/// Trait for quirk: Clothes Eater +#define TRAIT_CLOTH_EATER "cloth_eater" +/// Trait for quirk: Werewolf +#define TRAIT_WEREWOLF "werewolf" +/// Trait for quirk: Buns of Steel +#define TRAIT_STEEL_ASS "steel_ass" // Use TRAIT_PERSONALSPACE instead +/// Trait for quirk: Buns of Thunder +#define TRAIT_JIGGLY_ASS "jiggly_ass" +/// Trait for quirk: Cursed Blood +#define TRAIT_CURSED_BLOOD "cursed_blood" +/// Trait for quirk: Headpat Slut +#define TRAIT_HEADPAT_SLUT "headpat_slut" +/// UNUSED - Legacy trait for quirk: Distant +#define TRAIT_DISTANT "headpat_hater" // Use TRAIT_BADTOUCH instead +/// Trait for quirk: Dorsualiphobic Augmentation +#define TRAIT_HIDE_BACKPACK "hide_backpack" +/// Trait for quirk: Dumb For Cum +#define TRAIT_DUMB_CUM "dumb_for_cum_base" +/// Trait used by Dumb For Cum quirk when 'craving' +#define TRAIT_DUMB_CUM_CRAVE "dumb_for_cum_need" +/// Trait for quirk: Undeath +#define TRAIT_UNDEAD "Undead" +/// Trait for quirk: Illuminated +#define TRAIT_COSGLOW "cosmetic_glow" +/// Trait for quirk: Body Morpher +#define TRAIT_BODY_MORPHER "body_morpher" +/// Trait for quirk: Hallowed +#define TRAIT_HALLOWED "hallowed" +/// Trait for quirk: Messy +#define TRAIT_MESSY "messy" +/// Trait for quirk: Restorative Metabolism +#define TRAIT_RESTORATIVE_METABOLISM "restorative_metabolism" +/// Trait for quirk: Kiss Slut +#define TRAIT_KISS_SLUT "kiss_slut" +/// Trait for quirk: Flimsy +#define TRAIT_FLIMSY "flimsy" +/// Trait for quirk: Gargoyle +#define TRAIT_GARGOYLE "gargoyle" +/// Trait for quirk: Bane Syndrome +#define TRAIT_MASKED_MOOK "masked_mook" +/// Trait for quirk: Tough +#define TRAIT_TOUGH "tough" +/// Trait for quirk: Thirsty +#define TRAIT_THIRSTY "thirsty" +/// Trait for quirk: Concubus +#define TRAIT_CONCUBUS "concubus" +/// Trait for quirk: Modular Limbs +#define TRAIT_MODULAR_LIMBS "modular_limbs" +/// Trait for quirk: Vacuum Resistance +#define TRAIT_VACUUM_RESIST "vacuum_resist" +/// Trait for quirk: Overweight +#define TRAIT_OVERWEIGHT "overweight" + +/// +// Element traits +/// +/// Trait used by the Chapel Weakness element +#define TRAIT_CHAPEL_WEAKNESS "chapel_weakness" +/// Trait used by the Holy Water Weakness element +#define TRAIT_HOLYWATER_WEAKNESS "holywater_weakness" +/// Trait used by Flutter Move element +#define TRAIT_FLUTTER_MOVE "flutter_move" diff --git a/code/__DEFINES/~~~splurt_defines/traits/defines.dm b/code/__DEFINES/~~~splurt_defines/traits/defines.dm new file mode 100644 index 0000000000000..a7f473321ea51 --- /dev/null +++ b/code/__DEFINES/~~~splurt_defines/traits/defines.dm @@ -0,0 +1,57 @@ +// Quirk mood types +#define QMOOD_NUDIST "mood_nudist" +#define QMOOD_MASKED_MOOK "mood_masked_mook" +#define QMOOD_HIDE_BAG "mood_storage_concealment" +#define QMOOD_WELL_TRAINED "mood_dom_trained" +#define QMOOD_DUMB_CUM "mood_dumb4cum" +#define QMOOD_HEADPAT_SLUT "mood_headpat_slut" +#define QMOOD_JIGGLY_ASS "mood_jiggly_ass" +#define QMOOD_BADTOUCH_ATTACKER "mood_badtouch_attacker" +#define QMOOD_BADTOUCH_VICTIM "mood_badtouch_victim" + +// Bloodfledge mood types +#define QMOOD_BFLED_DRANK_MATCH "bloodfledge_drank_exotic_match" +#define QMOOD_BFLED_DRANK_SYNTH "bloodfledge_drank_synth" +#define QMOOD_BFLED_DRANK_SLIME "bloodfledge_drank_slime" +#define QMOOD_BFLED_DRANK_BUG "bloodfledge_drank_insect" +#define QMOOD_BFLED_DRANK_XENO "bloodfledge_drank_xeno" +#define QMOOD_BFLED_DRANK_DEAD "bloodfledge_drank_dead" +#define QMOOD_BFLED_DRANK_KILL "bloodfledge_drank_killed" +#define QMOOD_BFLED_DRANK_CURSE "bloodfledge_drank_cursed_blood" +#define QMOOD_BFLED_DRANK_VAMP "bloodfledge_drank_dead" +#define QMOOD_BFLED_DRANK_POD "bloodfledge_drank_podperson" +#define QMOOD_BFLED_DRANK_SNAIL "bloodfledge_drank_snail" +#define QMOOD_BFLED_DRANK_ETHER "bloodfledge_drank_ethreal" +#define QMOOD_BFLED_DRANK_SKREL "bloodfledge_drank_skrell" +#define QMOOD_BFLED_DRANK_BLOOD_SELF "bloodfledge_drank_own_blood" +#define QMOOD_BFLED_DRANK_BLOOD_FAKE "bloodfledge_drank_fake_blood" + +// Trait types +#define DUMB_CUM_TRAIT "dumb4cum" + +// Quirk variables +//#define RADFIEND_IMMUNITY_TIME (5 MINUTES) // How long does Rad Fiend protect against radiation? Currently unused. +// Amount of nutrition Concubus gets from compatible reagents +#define CONCUBUS_NUTRITION_AMT 10 // Slightly below Nutriment (15) + +/// +/// Quirk examine text status effects +/// +/// Examine text status effect for Bad Touch +#define QUIRK_EXAMINE_BADTOUCH "quirk_examine_badtouch" +/// Examine text status effect for Illuminated +#define QUIRK_EXAMINE_COSGLOW "quirk_examine_cosglow" +/// Examine text status effect for Headpat Slut +#define QUIRK_EXAMINE_HEADPAT_SLUT "quirk_examine_headpat_slut" +/// Examine text status effect for Hypnotic Gaze +#define QUIRK_EXAMINE_HYPNOTIC_GAZE "quirk_examine_hypnotic_gaze" +/// Examine text status effect for Buns of Thunder +#define QUIRK_EXAMINE_JIGGLY_ASS "quirk_examine_jiggly_ass" +/// Examine text status effect for Bane Syndrome +#define QUIRK_EXAMINE_MASKED_MOOK "quirk_examine_masked_mook" +/// Examine text status effect for Nudist +#define QUIRK_EXAMINE_NUDIST "quirk_examine_nudist" +/// Examine text status effect for Personal Space +#define QUIRK_EXAMINE_PERSONALSPACE "quirk_examine_personalspace" +/// Examine text status effect for Hallowed +#define QUIRK_EXAMINE_HALLOWED "quirk_examine_hallowed" diff --git a/code/__HELPERS/~splurt_helpers/is_helpers.dm b/code/__HELPERS/~splurt_helpers/is_helpers.dm new file mode 100644 index 0000000000000..dd4d20a271afa --- /dev/null +++ b/code/__HELPERS/~splurt_helpers/is_helpers.dm @@ -0,0 +1 @@ +#define isbloodfledge(A) (HAS_TRAIT(A, TRAIT_BLOODFLEDGE)) diff --git a/code/__HELPERS/~splurt_helpers/mob.dm b/code/__HELPERS/~splurt_helpers/mob.dm index 0a39b5c7800b9..b5d205c711620 100644 --- a/code/__HELPERS/~splurt_helpers/mob.dm +++ b/code/__HELPERS/~splurt_helpers/mob.dm @@ -1,5 +1,3 @@ - - /proc/random_unique_arachnid_name(attempts_to_find_unique_name=10) for(var/i in 1 to attempts_to_find_unique_name) . = capitalize(pick(GLOB.arachnid_first)) + " " + capitalize(pick(GLOB.arachnid_last)) @@ -10,7 +8,6 @@ /proc/arachnid_name() return "[pick(GLOB.arachnid_first)] [pick(GLOB.arachnid_last)]" - /proc/resolve_intent_name(intent) switch(intent) if(INTENT_HELP) @@ -21,3 +18,57 @@ return "grab" if(INTENT_HARM) return "harm" + +/mob/living/proc/is_body_part_exposed(body_part, list/items) + if(!items) + items = get_equipped_items() + for(var/A in items) + var/obj/item/I = A + if(istype(I) && (I.body_parts_covered & body_part)) + return FALSE + return TRUE + +/mob/living/carbon/proc/get_blood_prefix() + // Check for hemophage + if(ishemophage(src)) + return "Hemo" + + // Check for Synthetic + else if(issynthetic(src)) + return "Oil" + + // Check for Teshari + else if(isteshari(src)) + return "Ammonia" + + // Check for Shadekin + else if(isshadekin(src)) + return "Shade" + + // Check for round-start Slime + else if(isroundstartslime(src)) + return "Slime" + + // Check for Snail + else if(issnail(src)) + return "Lube" + + // Check for Skrell + else if(is_species(src,/datum/species/skrell)) + return "Copper" + + // Check for Xenomorph Hybrid + else if(isxenohybrid(src)) + return "Acid" + + // Check for Ethreal + else if(isethereal(src)) + return "Electro" + + // Check for Podperson + else if(ispodperson(src)) + return "Hydro" + + // Check for Plasmaman + else if(isplasmaman(src)) + return "Plasma" diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index 305eb0a90629e..4387d9ff44b59 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -42,8 +42,56 @@ GLOBAL_LIST_INIT_TYPED(quirk_blacklist, /list/datum/quirk, list( //BUBBER EDIT ADDITION BEGIN list(/datum/quirk/featherweight, /datum/quirk/oversized), list(/datum/quirk/overweight, /datum/quirk/obese), - list(/datum/quirk/dominant_aura, /datum/quirk/well_trained) + list(/datum/quirk/dominant_aura, /datum/quirk/well_trained), //BUBBER EDIT ADDITION END + //SPLURT EDIT ADDITION BEGIN + + // Hallowed is a direct foil to both quirks. + // Causes a conflict with Holy Water effects. + // Bloodsuckers cannot interact with Hallowed users. + list(/datum/quirk/hallowed, /datum/quirk/cursed_blood), + list(/datum/quirk/hallowed, /datum/quirk/item_quirk/bloodfledge), + + // Bloodfledges do not use standard thirst + list(/datum/quirk/item_quirk/bloodfledge, /datum/quirk/thirsty), + + // Causes a direct mechanical conflict. + //list(/datum/quirk/jiggly_ass,/datum/quirk/buns_of_steel), + list(/datum/quirk/jiggly_ass,/datum/quirk/personalspace), + + // Both quirks add a cosmetic glow effect. + list(/datum/quirk/rad_fiend, /datum/quirk/cosglow), + + // You have an irremovable suit. + list(/datum/quirk/nudist,/datum/quirk/equipping/entombed), + + // Conflicting health add-remove effect. + //list(/datum/quirk/flimsy,/datum/quirk/tough), + + // You must have working eyes for hypnotic gaze. + // NOTE: Quirk was replaced by NIFsoft + list(/datum/quirk/hypnotic_gaze,/datum/quirk/item_quirk/blindness), + list(/datum/quirk/hypnotic_gaze,/datum/quirk/echolocation), + + // You can't smell anything! + list(/datum/quirk/cum_sniff,/datum/quirk/item_quirk/anosmia), + + // You can't choke if you don't breathe + list(/datum/quirk/choke_slut, /datum/quirk/breathless), + + // Direct mechanical conflict + list(/datum/quirk/bad_touch, /datum/quirk/headpat_slut), + list(/datum/quirk/bad_touch, /datum/quirk/excitable), + + // Addict quirks fail if the mob has TRAIT_LIVERLESS_METABOLISM + // Any quirk that gives this trait should be blacklisted + list(/datum/quirk/item_quirk/addict/alcoholic, /datum/quirk/item_quirk/bloodfledge), + list(/datum/quirk/item_quirk/addict/alcoholic, /datum/quirk/concubus), + list(/datum/quirk/item_quirk/addict/junkie, /datum/quirk/item_quirk/bloodfledge), + list(/datum/quirk/item_quirk/addict/junkie, /datum/quirk/concubus), + list(/datum/quirk/item_quirk/addict/smoker, /datum/quirk/item_quirk/bloodfledge), + list(/datum/quirk/item_quirk/addict/smoker, /datum/quirk/concubus), + //SPLURT EDIT ADDITION END )) GLOBAL_LIST_INIT(quirk_string_blacklist, generate_quirk_string_blacklist()) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index cc02c191735bb..f296bd18249ea 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -100,8 +100,12 @@ qdel(src) /obj/item/clothing/attack(mob/living/target, mob/living/user, params) - if(user.combat_mode || !ismoth(target) || ispickedupmob(src)) + // SPLURT EDIT - Cloth Eater quirk + if(user.combat_mode || ispickedupmob(src)) return ..() + if(!(ismoth(target) || HAS_TRAIT(target, TRAIT_CLOTH_EATER))) // Moth OR Cloth Eater + return ..() + // SPLURT EDIT END if((clothing_flags & INEDIBLE_CLOTHING) || (resistance_flags & INDESTRUCTIBLE)) return ..() if(isnull(moth_snack)) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 734167fae6a11..7144381445205 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -345,12 +345,44 @@ if(HAS_TRAIT(src, TRAIT_BADTOUCH)) to_chat(helper, span_warning("[src] looks visibly upset as you pat [p_them()] on the head.")) + src.badtouch_retaliate(helper) // SPLURT EDIT - Distant //SKYRAT EDIT ADDITION BEGIN - EMOTES if(HAS_TRAIT(src, TRAIT_EXCITABLE)) var/obj/item/organ/external/tail/src_tail = get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) if(src_tail && !(src_tail.wag_flags & WAG_WAGGING)) emote("wag") //SKYRAT EDIT ADDITION END + // SPLURT EDIT BEGIN - Headpat Slut quirk + if(HAS_TRAIT(src, TRAIT_HEADPAT_SLUT)) + // Display messages to participants + to_chat(helper, span_purple("[src] seems to be enjoying the head patting way more than normal...")) + to_chat(src, span_purple("[helper] sends a wave of pleasure through your head with [helper.p_their()] touch!")) + + // Add mood events + add_mood_event(QMOOD_HEADPAT_SLUT, /datum/mood_event/headpat_slut/recipient, helper) + helper.add_mood_event("petting_bonus", /datum/mood_event/headpat_slut/giver, src) + + // Define target + var/mob/living/carbon/human/target_mob = src + + // Increase arousal + target_mob?.adjust_arousal(5) + target_mob?.adjust_pleasure(2.5) + + // Small chance of additional effects + if(prob(10)) + // Attempt to auto-emote + try_lewd_autoemote(pick("moan", "blush")) + + // Increase dizziness + set_dizzy_if_lower(3 SECONDS) + + // Check for well trained + if(has_quirk(/datum/quirk/well_trained)) + // Induce helplessness + Stun(5) + + // SPLURT EDIT END else if ((helper.zone_selected == BODY_ZONE_PRECISE_GROIN) && !isnull(src.get_organ_by_type(/obj/item/organ/external/tail))) helper.visible_message(span_notice("[helper] pulls on [src]'s tail!"), \ @@ -359,6 +391,7 @@ to_chat(src, span_notice("[helper] pulls on your tail!")) if(HAS_TRAIT(src, TRAIT_BADTOUCH)) //How dare they! to_chat(helper, span_warning("[src] makes a grumbling noise as you pull on [p_their()] tail.")) + src.badtouch_retaliate(helper) // SPLURT EDIT - Distant else add_mood_event("tailpulled", /datum/mood_event/tailpulled) @@ -428,6 +461,7 @@ if(HAS_TRAIT(src, TRAIT_BADTOUCH)) to_chat(helper, span_warning("[src] looks visibly upset as you hug [p_them()].")) + src.badtouch_retaliate(helper) // SPLURT EDIT - Distant SEND_SIGNAL(src, COMSIG_CARBON_HELP_ACT, helper) SEND_SIGNAL(helper, COMSIG_CARBON_HELPED, src) diff --git a/html/changelogs/AutoChangeLog-pr-10.yml b/html/changelogs/AutoChangeLog-pr-10.yml new file mode 100644 index 0000000000000..0588440ec20f8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10.yml @@ -0,0 +1,163 @@ +author: "SpaghettiTerrible" +delete-after: True +changes: + - rscadd: "Ported quirk: Modular Limbs" + - rscadd: "Ported quirk: Rad Fiend" + - rscadd: "Ported quirk: Sodium Sensitivity" + +author: "MosleyTheMalO" +delete-after: True +changes: + - rscadd: "Ported quirk: Bane Syndrome" + - rscadd: "Ported quirk: Body Morpher" + - rscadd: "Ported quirk: Buns of Steel" + - rscadd: "Ported quirk: Buns of Thunder" + - rscadd: "Ported quirk: Cloth Eater" + - rscadd: "Ported quirk: Cosmetic Glow" + - rscadd: "Ported quirk: Cursed Blood" + - rscadd: "Ported quirk: Dorsualiphobic Augmentation" + - rscadd: "Ported quirk: Flutter" + - rscadd: "Ported quirk: Gargoyle" + - rscadd: "Ported quirk: Genital Sniffer" + - rscadd: "Ported quirk: Hallowed" + - rscadd: "Ported quirk: Hypnotic Gaze" + - rscadd: "Ported quirk: Incubus" + - rscadd: "Ported quirk: Kiss Slut" + - rscadd: "Ported quirk: Nudist" + - rscadd: "Ported quirk: Restorative Metabolism" + - rscadd: "Ported quirk: Succubus" + - rscadd: "Ported quirk: Tough" + - rscadd: "Ported quirk: Undeath" + - rscadd: "Ported quirk: Vacuum Resistance" + - rscadd: "Ported implant: Storage Concealment Implant" + - rscadd: "Ported item: Aesthetic Gas Mask" + - rscadd: "Ported ghost slapping" + - rscadd: "Ported clothing burst effect" + - rscadd: "Added medical text for Modular Limbs" + - rscadd: "Added medical text for Rad Fiend" + - rscadd: "Added headpat heart effect to Excitable quirk" + - refactor: "Merged Rad Fiend glow action into Cosmetic Glow" + - refactor: "Updated signal handling method for quirk Nudist" + +author: "LeDrascol" +delete-after: True +changes: + - rscadd: "Ported quirk: Ashen Resistance" + - rscadd: "Ported quirk: Arachnid (as Silkspinner)" + - rscadd: "Ported quirk: Bloodsucker Fledgling" + - rscadd: "Ported quirk: Breathless" + - rscadd: "Ported quirk: Choke Slut" + - rscadd: "Ported quirk: Distant (as Bad Touch)" + - rscadd: "Ported quirk: Headpat Slut" + - rscadd: "Ported quirk: Extra-Productive Genitals" + - rscadd: "Ported reagent: Strange Nutriment" + - rscadd: "Added quirk Concubus - merging together Incubus and Succubus" + - rscadd: "Added NIFSoft variant for Genital Fluid Inducer implant" + - rscadd: "Added NIFSoft variant for Rapid Disrobe implant" + - rscadd: "Added NIFSoft variant for Chameleon Storage Concealment implant" + - rscadd: "Added secret brainwashing variant of Libidine Eye (NIFSoft)" + - rscadd: "Added secret brainwashing NIF to research techweb" + - rscadd: "Added unique quirk IDs for Bloodfledge, Concubus, Gargoyle, Hallowed, Rad Fiend, and Werewolf (mostly unused)" + - rscadd: "Headpat Slut: Added new mood events" + - rscadd: "Bad Touch: Added new mood events" + - rscadd: "Bad Touch: Added Sadist and Masochist support" + - rscadd: "Bad Touch: Added quirk examine text" + - rscadd: "Personal Space: Added quirk examine text" + - rscadd: "Buns of Thunder: Added ghost slapping support" + - rscadd: "Modular Limbs: Added more dynamic interaction feedback" + - rscadd: "Modular Limbs: Added Autotomy ability" + - rscadd: "Modular Limbs: Added easy limb reattachment and dismemberment" + - rscadd: "Sodium Sensitivity: Added salt metabolize feedback messages" + - rscadd: "Arachnid: Added web movement and cloth weaving abilities" + - rscadd: "Illuminated: Added animation to glow outline" + - rscadd: "Illuminated: Added TGUI suppourt to glow control" + - rscadd: "Illuminated: Added actual light source" + - rscadd: "Hypnotic Gaze: Added some missing can use checks" + - rscadd: "Bloodfledge: Added compatability with Hemophage" + - rscadd: "Bloodfledge: Added compatability with Do Not Revive" + - rscadd: "Bloodfledge: Added compatability with all new exotic blood types" + - rscadd: "Bloodfledge: Added new sprite for Quirk ID card" + - rscadd: "Bloodfledge: Added the bloodfledge-exclusive Hemorrhagic Sanguinizer" + - rscadd: "Bloodfledge: Added balloon alerts to bite failures" + - rscadd: "Bloodfledge: Added check for biting robotic eyes" + - rscadd: "Bloodfledge: Added preference for bloody food" + - rscadd: "Bloodfledge: Added penalty for being staked" + - rscadd: "Bloodfledge: Added mood event for drinking artifical blood" + - rscadd: "Bloodfledge: Added mood event for drinking your own blood" + - rscadd: "Bloodfledge: Added examine text to indicate having a self-revive ability" + - rscadd: "Bloodfledge: Added vampiric biotype assignment" + - rscadd: "Bloodfledge: Added back chapel location effects" + - rscadd: "Bloodfledge: Quirk ID is now named based on your species blood type" + - rscadd: "Bloodfledge: Quirk ID now uses your operative name" + - rscadd: "Bloodfledge: Quirk ID uses the newer advanced ID system" + - rscadd: "Cursed Blood: Added distinct text styling to forced speech phrases" + - rscadd: "Cursed Blood: Added status effect alert for Holy Water interaction (Dreaming)" + - rscadd: "Cursed Blood: Added 20 new Dreaming messages" + - rscadd: "Cursed Blood: Added chapel location effects" + - rscadd: "Cursed Blood: Added support for five new types of deities" + - rscadd: "Cursed Blood: Some dreaming messages now use your deity's name" + - rscadd: "Flutter: Zero-gravity movement now has proper momentum" + - rscadd: "Flutter: Now allows freely drifting while near walls" + - rscadd: "Hemophage: Added chapel and holy water effects to match Bloodfledge" + - rscadd: "Hemophage: Added respiratory organs" + - rscadd: "Added mail goodies for Bane Syndrome, Body Morpher, Buns of Thunder, Choke Slut, Cosmetic Glow, Cursed Blood, Dumb For Cum, Gargoyle, Hallowed, Incubus, Kiss Slut, Messy, Rad Fiend, Restorative Metabolism, Succubus, and Vacuum Resistance" + - rscadd: "Added hardcore values for Ashen Resistance, Bloodsucker Fledgling, Breathless, Flimsy, Flutter, Restorative Metabolism, Sodium Sensitivity, Tough, and Vacuum Resistance" + - rscadd: "Updated quirk flavor text for Ashen Resistance, Bane Syndrome, Bloodsucker Fledgling, Body Morpher, Breathless, Buns of Steel, Buns of Thunder, Clothes Eater, Cosmetic Glow, Cursed Blood, Distant, Dumb For Cum, Fluid Infuser, Flutter, Genital Sniffer, Hallowed, Headpat Slut, Hypnotic Gaze, Incubus, Kiss Slut, Messy, Modular Limbs, Nudist, Overweight, Rad Fiend, Silkspinner, Sodium Sensitivity, Succubus, Thirsty, Tough, Undeath, Vacuum Resistance" + - rscadd: "Added quirk medical records, gain text, and loss text to all ported quirks that did not have it" + - rscadd: "Added traits for all ported quirks that did not have one" + - rscdel: "Bane Syndrome: No longer starts with a free mask" + - rscdel: "Undeath: Removed hunger penalty" + - rscdel: "Undeath: Removed fake health HUD" + - rscdel: "Modular Limbs: Removed quirk self-interacting" + - rscdel: "Vacuum Resist: Removed some incorrect traits" + - rscdel: "Nudist: Removed Rapid Disrobe implant in favor of NIFSoft" + - rscdel: "Hemophage: Removed environmental hazard immunity traits" + - rscdel: "Hemophage: Removed veteran status requirement" + - balance: "Shapeshifter (NIFSoft) now persists between shifts" + - balance: "Shapeshifter can be used on standard NIFs" + - balance: "NIFs now have twice as many slots" + - balance: "Body Morpher and Libidine Eye cost less energy" + - balance: "Weight quirks no longer apply a permanent movement peanlty" + - balance: "Alternative nutrition quirks cannot be taken with addiction quirks due to a code conflict" + - balance: "Hallowed: Holy water effect better foils Cursed Blood" + - balance: "Concubus: Loses nutrition 1% faster" + - balance: "Concubus: Gains signifigantly more nutrition from sources" + - balance: "Concubus: Requires breast milk instead of any milk" + - balance: "Hard Soles: Now a neutral quirk" + - balance: "Overweight: Now a neutral quirk" + - balance: "Bad Touch: Now a neutral quirk" + - balance: "Obese: Point cost adjusted to match old Overweight cost" + - balance: "Rad Fiend: Now completely immune to radiation" + - balance: "Bloodfledge: Restored revival damage threshold checks" + - balance: "Bloodfledge: Reviving now heals all damage up to a threshold" + - balance: "Bloodfledge: Revive nutrition cost is signifigantly lower" + - balance: "Bloodfledge: Now burned by being splashed with Holy Water" + - balance: "Cursed Blood: Holy Water effects can be applied on contact" + - balance: "Cursed Blood: Increased potential severity effects for drinking Holy Water" + - bugfix: "Hallowed: Fixed applying the incorrect type of anti-magic" + - bugfix: "Illuminated: Fixed missing icon" + - bugfix: "Illuminated: Limited maximum opacity to prevent crew becoming stickers" + - bugfix: "Hypnotic Gaze: Fixed missing pronouns" + - bugfix: "Modular Limbs: Fixed verb working on irremovable limbs" + - bugfix: "Restorative Metabolism: Fixed thresholds not being applied" + - bugfix: "Sodium Sensitivity: Now works with salt water" + - bugfix: "Dumb For Cum and Concubus now works with female-specific reagents" + - sound: "Changed sound for Hypnotic Gaze (Libidine Eye) activation" + - sound: "Changed sound for Buns of Steel (Personal Space) retaliation" + - sound: "Added sound effect to Modular Limbs interaction" + - sound: "Added sound effect to Sodium Sensitivity burning" + - refactor: "Quirks involving reagent processing now use Signals" + - refactor: "Merged Hypnotic Gaze (quirk) into Libidine Eye (NIFSoft)" + - refactor: "Merged Distant quirk into Bad Touch" + - refactor: "Merged Buns of Steel quirk into Personal Space" + - refactor: "Renamed quirk Cosmetic Glow to Illuminated" + - refactor: "Renamed quirk Arachnid to Silkspinner" + - refactor: "Renamed quirk Bloodsucker Fledgling to Bloodfledge" + - refactor: "Use new method for applying quirk examine text" + - refactor: "ERP quirks use purple gain and lose text" + - refactor: "Negative quirks use red gain text" + - refactor: "Bane Syndrome now uses signals instead of processing" + - refactor: "Hemophages now use the Bloodfledge's bite action" + - refactor: "Updated ghost slapping to support all ass slapping quirks" + - refactor: "Undeath: Updated traits to better match actual zombies" + - refactor: "Cursed Blood: Hell Water bonus now matches Hallowed Holy Water bonus" diff --git a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm index 104674322bf68..a4c8c20f2c1a3 100644 --- a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm +++ b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm @@ -131,6 +131,8 @@ having to expend any blood to maintain minimal bodily functions \ so long as their host remains stationary in said locker.", ), + // SPLURT EDIT - REMOVAL + /* list( SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, SPECIES_PERK_ICON = "biohazard", @@ -139,6 +141,8 @@ other viruses and don't actually require an external source of oxygen \ to stay alive.", ), + */ + // SPLURT EDIT END list( SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, SPECIES_PERK_ICON = "tint", diff --git a/modular_skyrat/modules/modular_implants/code/nifsoft_catalog.dm b/modular_skyrat/modules/modular_implants/code/nifsoft_catalog.dm index d60dd4ac3f4e2..f6424531b72ce 100644 --- a/modular_skyrat/modules/modular_implants/code/nifsoft_catalog.dm +++ b/modular_skyrat/modules/modular_implants/code/nifsoft_catalog.dm @@ -7,7 +7,7 @@ GLOBAL_LIST_INIT(purchasable_nifsofts, list( /datum/nifsoft/soulcatcher, /datum/nifsoft/scryer, /datum/nifsoft/summoner/book, - /datum/nifsoft/action_granter/hypnosis, + /datum/nifsoft/action_granter/hypnosis )) /datum/computer_file/program/nifsoft_downloader diff --git a/modular_zzplurt/code/_onclick/observer.dm b/modular_zzplurt/code/_onclick/observer.dm new file mode 100644 index 0000000000000..c108489addbe5 --- /dev/null +++ b/modular_zzplurt/code/_onclick/observer.dm @@ -0,0 +1,53 @@ +/mob/living/carbon/human/attack_ghost(mob/dead/observer/user) + . = ..() + if(. || !user.client) + return + if(!(isAdminGhostAI(user) || user.client.prefs.read_preference(/datum/preference/toggle/inquisitive_ghost)) && CONFIG_GET(flag/ghost_interaction)) + return + ghost_ass_slap(user) + +/mob/living/carbon/human/proc/ghost_ass_slap(mob/dead/observer/user) + // Check if target is alive + if(stat >= DEAD) + // Alert ghost and return + to_chat(user, "Your ethereal hand phases through \The [src]. This will only work on the living.") + return + + // Check for Personal Space (formally Buns of Steel) + if(HAS_TRAIT(src, TRAIT_PERSONALSPACE)) + // Display messages + visible_message(\ + span_danger("[src]'s ass clangs as though it were being smacked, but nobody is there."), + span_danger("You feel something bounce off your steely asscheeks, but nothing is there..."), + "You hear a loud clang!", null, user) + to_chat(user, span_notice("You try to slap \The [src]'s ass, but your ethereal hand bounces right off!")) + + // Play reflect sound + playsound(src.loc, get_sfx_skyrat(SFX_BULLET_IMPACT_METAL), 50, 1, -1) + + return + + /* + if(!HAS_TRAIT(src, TRAIT_PERMABONER)) + H.dna.species.stop_wagging_tail(src) + */ + + // Play slap sound + conditional_pref_sound(src.loc, 'sound/items/weapons/slap.ogg', 50, 1, -1) + + // Display standard message + visible_message(\ + span_danger("You hear someone slap \The [src]'s ass, but nobody's there..."), + span_notice("Somebody slaps your ass, but nobody is around..."), + "You hear a spooky slap.", null, user) + to_chat(user, span_notice("You manage to will your ethereal hand to slap \The [src]'s ass.")) + + // Check for slapping quirk + if(HAS_TRAIT(src, TRAIT_JIGGLY_ASS)) + // Check for non-con pref + if(READ_PREFS(src, choiced/erp_status_nc) != "Yes") + // Don't do it + return + + // Add arousal + adjust_arousal(10) diff --git a/modular_zzplurt/code/datums/components/crafting/equipment.dm b/modular_zzplurt/code/datums/components/crafting/equipment.dm new file mode 100644 index 0000000000000..9e0e27dfac02a --- /dev/null +++ b/modular_zzplurt/code/datums/components/crafting/equipment.dm @@ -0,0 +1,33 @@ +// Bloodfledge exclusive emag +/datum/crafting_recipe/emag_bloodfledge + name = "Hemorrhagic Sanguinizer" + desc = "A modified Bloodfledge ID card capable of channeling technopathic blood magic." + reqs = list( + /obj/item/card/id/advanced/quirk/bloodfledge = 1, + /obj/item/assembly/signaler = 1, + /obj/item/reagent_containers/syringe = 1, + /obj/item/reagent_containers/blood = 1, + /obj/item/stack/cable_coil = 5, + ) + result = /obj/item/card/emag/bloodfledge + tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_MULTITOOL) + category = CAT_EQUIPMENT + crafting_flags = CRAFT_MUST_BE_LEARNED + +// Bloodfledge emag post-crafting +/datum/crafting_recipe/emag_bloodfledge/on_craft_completion(mob/user, atom/result) + // Define carbon holder + var/mob/living/carbon/item_mob = user + + // Check for valid holder + if(!item_mob) + // Do nothing + return + + // Get blood prefix + var/blood_name = item_mob?.get_blood_prefix() + + // Check if blood prefix exists + if(blood_name) + // Rename the card + result.name = LOWER_TEXT("[blood_name]rrhagic Sanguinizer") diff --git a/modular_zzplurt/code/datums/components/irradiated.dm b/modular_zzplurt/code/datums/components/irradiated.dm new file mode 100644 index 0000000000000..f682fa5030878 --- /dev/null +++ b/modular_zzplurt/code/datums/components/irradiated.dm @@ -0,0 +1,31 @@ +// Handle radiation damage +// Currently unused +/* +/datum/component/irradiated/process_tox_damage(mob/living/carbon/human/target, seconds_per_tick) + // Check for Rad Fiend quirk + if(HAS_TRAIT(target_mob, TRAIT_RAD_FIEND)) + // Check if time threshold is met + if(beginning_of_irradiation < RADFIEND_IMMUNITY_TIME) + // Return without effects + return + + // Run normally + . = ..() +*/ + +// Radiation proc to check for Rad Fiend quirk +// Currently unused +/* +/datum/component/irradiated/proc/check_rad_fiend(mob/living/carbon/human/target) + // Check if holder has Rad Fiend quirk + if(HAS_TRAIT(parent, TRAIT_RAD_FIEND)) + return FALSE + + // Check if time threshold is met + if((world.time - beginning_of_irradiation) < RADFIEND_IMMUNITY_TIME) + // Quirk should protect the holder! + return TRUE + + // Holder is not protected! + return FALSE +*/ diff --git a/modular_zzplurt/code/datums/elements/chapel_weakness.dm b/modular_zzplurt/code/datums/elements/chapel_weakness.dm new file mode 100644 index 0000000000000..c7165ad3a7577 --- /dev/null +++ b/modular_zzplurt/code/datums/elements/chapel_weakness.dm @@ -0,0 +1,180 @@ +/// Phrases said to the holder when entering the chapel +#define CHAPELWEAK_PHRASES_ENTER pick(\ + "Your shadow creeps across the chapel's threshold. Seek solace from the divine, before the holy fire consumes you whole.",\ + "A sense of devotion envelops you, a bittersweet reminder of what you've lost.",\ + "The chapel's warmth is a cruel mockery, reminding you of what can never be yours.",\ + "In this sanctuary of light, you're reminded that even the purest faith cannot banish the shadows within.",\ + "You feel an unseen force from beyond pushing back against your presence, as if the very air resists your shadow.",\ + "The chapel's hallowed halls seem to whisper judgment against you, their silence a heavy weight.",\ + "An unspoken verdict hangs in the air, condemning you without a word."\ + ) + +/// Phrases said to the holder when exiting the chapel +#define CHAPELWEAK_PHRASES_EXIT pick(\ + "Your shadows retreats from the chapel, leaving behind only a memory of its presence.",\ + "As you leave, the chapel's influence wanes, but your darkness remains unvanquished.",\ + "The chapel's shadow retreats with yours, its influence receding from your presence.",\ + "You rejoin the night, leaving behind the chapel's influence that threatened to consume you.",\ + "The chapel's peace is restored, but at what cost to your soul?",\ + "Your shadow retreats from the holy place, no longer tainting its sacred ground."\ + ) + +/// Phrases said to the holder when ignited by the chapel +#define CHAPELWEAK_PHRASES_IGNITE pick(\ + "Divine fury bathes your accursed form in cleansing flame.",\ + "Holy flame sears through the darkness that clings to you.",\ + "In this moment, your will is torn asunder – your shadow consumed by an unyielding fire that seems almost... hungry.",\ + "The holy fire seems to find an unexpected fuel in your very darkness – a macabre dance of light and shadow.",\ + "Your skin feels as if it's being peeled away, exposing the dark core that lies beneath.",\ + "The flames dance with an otherworldly glee, as if they've discovered a long-forgotten secret within you.",\ + "The flames reveal your true nature: a creature forged in darkness.",\ + "Your shadow is repressed amidst the crackling inferno that engulfs your form, a living torch of divine vengeance.",\ + "Your shadow is consumed by the very fire it sought to escape."\ + ) + +/// Percentage chance of burning each tick while in the chapel +#define CHAPELWEAK_CHANCE_IGNITE 10 +/// Amount of fire stacks to add when igniting +#define CHAPELWEAK_AMT_FIRESTACKS 2 +/// Amount of disgust added per tick +#define CHAPELWEAK_AMT_DISGUST 5 +/// Amount of stamina loss applied per tick +#define CHAPELWEAK_AMT_STAMLOSS 5 + +/** + * Chapel Weakness element + * + * A mob with this element will be penalized for entering the chapel. + * Penalty is ignored for the chaplain. + * + * Contains the following: + * * Enter and leave warning messages + * * Ticking disgust and stamina penalty + * * Chance to spontaneously combust + */ +/datum/element/chapel_weakness + element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH_ON_HOST_DESTROY + argument_hash_start_idx = 2 + +/datum/element/chapel_weakness/Attach(datum/target) + . = ..() + + // Check for living target + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + // Register area change signals + RegisterSignal(target, COMSIG_ENTER_AREA, PROC_REF(on_entered)) + RegisterSignal(target, COMSIG_EXIT_AREA, PROC_REF(on_exited)) + +/datum/element/chapel_weakness/Detach(datum/source) + . = ..() + + // Unregister area change signals + UnregisterSignal(source, COMSIG_ENTER_AREA) + UnregisterSignal(source, COMSIG_EXIT_AREA) + + // Define living mob + var/mob/living/source_living = source + + // Remove status effect + source_living?.remove_status_effect(/datum/status_effect/chapel_weakness) + +/// Applied upon entering a new area +/datum/element/chapel_weakness/proc/on_entered(mob/living/mob_source, area/new_area) + SIGNAL_HANDLER + + // Check for valid mob + if(!mob_source) + return + + // Check if this is the chapel + if(!istype(new_area, /area/station/service/chapel)) + return + + // Check if mob is the chaplain + if(mob_source.mind?.assigned_role?.title == JOB_CHAPLAIN) + return + + // Add status effect + mob_source.apply_status_effect(/datum/status_effect/chapel_weakness) + +/// Applied upon exiting an area +/datum/element/chapel_weakness/proc/on_exited(mob/living/mob_source, area/old_area) + SIGNAL_HANDLER + + // Check for valid mob + if(!mob_source) + return + + // Check if the departed area is the chapel + if(!istype(old_area, /area/station/service/chapel)) + return + + // Check if the new area is also the chapel + if(istype(get_area(mob_source), /area/station/service/chapel)) + return + + // Remove status effect + mob_source.remove_status_effect(/datum/status_effect/chapel_weakness) + +// Status effect for Chapel Weakness that applies locational penalties +/datum/status_effect/chapel_weakness + id = "chapel_weakness_profane" + status_type = STATUS_EFFECT_REFRESH + alert_type = /atom/movable/screen/alert/status_effect/chapel_weakness_profane + processing_speed = STATUS_EFFECT_NORMAL_PROCESS + +// Status effect alert +/atom/movable/screen/alert/status_effect/chapel_weakness_profane + name = "Sacred Ground" + desc = "The area you are currently within is protected by a holy force." + icon_state = "terrified" + alerttooltipstyle = "cult" + +// Called when adding this status effect +/datum/status_effect/chapel_weakness/on_apply() + . = ..() + + // Warn user + to_chat(owner, span_cult(CHAPELWEAK_PHRASES_ENTER)) + +// Called when removing this status effect +/datum/status_effect/chapel_weakness/on_remove() + . = ..() + + // Alert user + to_chat(owner, span_cult(CHAPELWEAK_PHRASES_EXIT)) + +// Processing for status effect +/datum/status_effect/chapel_weakness/tick(seconds_between_ticks) + // Define if owner is alive + var/owner_alive = (owner.stat != DEAD) + + // Check if alive + if(owner_alive) + // Add disgust + owner.adjust_disgust(CHAPELWEAK_AMT_DISGUST) + + // Inflict stamina damage + owner.adjustStaminaLoss(CHAPELWEAK_AMT_STAMLOSS) + + // Chance to spontaneously combust + if(prob(CHAPELWEAK_CHANCE_IGNITE)) + // Check if alive + if(owner_alive) + // Warn user + to_chat(owner, span_cult_bold(CHAPELWEAK_PHRASES_IGNITE)) + + // Ignite mob + owner.adjust_fire_stacks(CHAPELWEAK_AMT_FIRESTACKS) + owner.ignite_mob() + +// Set status examine text +/datum/status_effect/chapel_weakness/get_examine_text() + return span_notice("[owner.p_They()] [owner.p_are()] visibly disturbed by something about this room.") + +#undef CHAPELWEAK_CHANCE_IGNITE +#undef CHAPELWEAK_AMT_FIRESTACKS +#undef CHAPELWEAK_AMT_DISGUST +#undef CHAPELWEAK_AMT_STAMLOSS diff --git a/modular_zzplurt/code/datums/elements/flutter_move.dm b/modular_zzplurt/code/datums/elements/flutter_move.dm new file mode 100644 index 0000000000000..0814d34824e77 --- /dev/null +++ b/modular_zzplurt/code/datums/elements/flutter_move.dm @@ -0,0 +1,87 @@ +/// Default amount of drift force to apply when flying +#define DEFAULT_FUNCTIONAL_FORCE 1 NEWTONS // Equal to moth wings +/// Default minimum air pressure to allow movement +#define DEFAULT_MIN_PRESSURE 86 // Roughly equal to moth wings + +/** + * Flutter movement element + * + * A mob with this element can move normally in pressurized zero-gravity environments. + */ +/datum/element/flutter_move + element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH_ON_HOST_DESTROY + argument_hash_start_idx = 2 + + /// Amount of drift force to apply when flying + var/functional_force = DEFAULT_FUNCTIONAL_FORCE + + /// Minimum air pressure to allow movement + var/min_pressure = DEFAULT_MIN_PRESSURE + +/datum/element/flutter_move/Attach(datum/target, input_force, input_pressure) + . = ..() + + // Check for living target + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + // Set functional force + if(input_force) + functional_force = input_force + + // Set minimum pressure + if(input_pressure) + min_pressure = input_pressure + + // Register movement signal + RegisterSignal(target, COMSIG_MOB_CLIENT_MOVE_NOGRAV, PROC_REF(on_moved)) + +/datum/element/flutter_move/Detach(datum/source) + . = ..() + + // Unregister movement signal + UnregisterSignal(source, COMSIG_MOB_CLIENT_MOVE_NOGRAV) + +/// Check if this mob should be allowed to move +/datum/element/flutter_move/proc/can_fly(mob/living/source) + // Check if being pulled + if(source.pulledby) + return FALSE + + // Check if being thrown + if(source.throwing) + return FALSE + + // Define current turf + var/turf/current_turf = get_turf(source) + + // Check if current turf exists + if(!current_turf) + return FALSE + + // Define current air + var/datum/gas_mixture/environment = current_turf.return_air() + + // Check for air pressure + if(environment?.return_pressure() < min_pressure) + return FALSE + + // All checks passed - Allow flying + return TRUE + +/// When moving, check for valid air pressure. Apply force if valid. +/datum/element/flutter_move/proc/on_moved(mob/living/source) + SIGNAL_HANDLER + + // Check if flight is allowed + if(!can_fly(source)) + return + + // Apply movement + // Based on code from functional_wings.dm + var/max_drift_force = (DEFAULT_INERTIA_SPEED / source.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1 + source.newtonian_move(dir2angle(source.client.intended_direction), instant = TRUE, drift_force = functional_force, controlled_cap = max_drift_force) + source.setDir(source.client.intended_direction) + +#undef DEFAULT_FUNCTIONAL_FORCE +#undef DEFAULT_MIN_PRESSURE diff --git a/modular_zzplurt/code/datums/elements/headpat.dm b/modular_zzplurt/code/datums/elements/headpat.dm new file mode 100644 index 0000000000000..9aa3f79af3398 --- /dev/null +++ b/modular_zzplurt/code/datums/elements/headpat.dm @@ -0,0 +1,4 @@ +/datum/element/pet_bonus/headpat/on_attack_hand(mob/living/pet, mob/living/petter, list/modifiers) + if(!(check_zone(petter.zone_selected) == BODY_ZONE_HEAD) || petter == pet) + return + . = ..() diff --git a/modular_zzplurt/code/datums/elements/holywater_weakness.dm b/modular_zzplurt/code/datums/elements/holywater_weakness.dm new file mode 100644 index 0000000000000..da6efb23878c2 --- /dev/null +++ b/modular_zzplurt/code/datums/elements/holywater_weakness.dm @@ -0,0 +1,71 @@ +/// Maximum damage taken when splashed with Holy Water +#define HWWEAK_SPLASH_DAMAGE_CAP 20 + +/** + * Holy Water weakness element + * + * A mob with this element will be penalized for interacting with holy water. + * + * When the reagent processes inside a mob: + * * Gain disgust + * * Lose nutrition + * + * When a mob is exposed to the reagent: + * * Play a burning sound + * * Apply fire damage based on amount + */ +/datum/element/holywater_weakness + element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH_ON_HOST_DESTROY + argument_hash_start_idx = 2 + +/datum/element/holywater_weakness/Attach(datum/target) + . = ..() + + // Check for living target + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + // Register holy water reagent interactions + RegisterSignal(target, COMSIG_REAGENT_PROCESS_HOLYWATER, PROC_REF(process_holywater)) + RegisterSignal(target, COMSIG_REAGENT_EXPOSE_HOLYWATER, PROC_REF(expose_holywater)) + +/datum/element/holywater_weakness/Detach(datum/source) + . = ..() + + // Unregister signals + UnregisterSignal(source, COMSIG_REAGENT_PROCESS_HOLYWATER) + UnregisterSignal(source, COMSIG_REAGENT_EXPOSE_HOLYWATER) + +/// Handle effects applied by digesting Holy Water +/datum/element/holywater_weakness/proc/process_holywater(mob/living/carbon/affected_mob) + SIGNAL_HANDLER + + // Check that target mob exists + if(!affected_mob) + return + + // Add disgust and reduce nutrition + affected_mob.adjust_disgust(2) + affected_mob.adjust_nutrition(-6) + +/// Handle effects applied by being exposed to Holy Water +/datum/element/holywater_weakness/proc/expose_holywater(mob/living/carbon/affected_mob, datum/reagent/handled_reagent, methods, reac_volume, show_message, touch_protection) + SIGNAL_HANDLER + + // Check that target mob exists + if(!affected_mob) + return + + // Play burning sound + playsound(affected_mob, SFX_SEAR, 30, TRUE) + + // Damage cap taken from bugkiller + // Intended to prevent instant crit from beaker splash + var/damage = min(round(0.4 * reac_volume, 0.1), HWWEAK_SPLASH_DAMAGE_CAP) + if(damage < 1) + return + + // Cause burn damage based on amount + affected_mob.adjustFireLoss(damage) + +#undef HWWEAK_SPLASH_DAMAGE_CAP diff --git a/modular_zzplurt/code/datums/id_trim/quirks.dm b/modular_zzplurt/code/datums/id_trim/quirks.dm new file mode 100644 index 0000000000000..abf790eaabf07 --- /dev/null +++ b/modular_zzplurt/code/datums/id_trim/quirks.dm @@ -0,0 +1,55 @@ +// This file contains all the trims associated with quirks. + +// Base entry +/datum/id_trim/quirk + trim_icon = 'modular_zzplurt/icons/obj/card.dmi' + trim_state = "trim_question" + assignment = "Error Reporter" + intern_alt_name = "Quirky" + department_color = COLOR_WEBSAFE_DARK_GRAY + subdepartment_color = "#00000000" // Disabled + +// Quirk: Bloodsucker Fledgling +/datum/id_trim/quirk/bloodfledge + trim_state = "trim_droplet_red" + department_color = "#00000000" // Disabled because of custom sprite + assignment = "Bloodsucker Fledgling" + intern_alt_name = "Fledgling" + threat_modifier = 2 // Equal to dress code violation + +// Quirk: Concubus +/datum/id_trim/quirk/concubus + trim_state = "trim_droplet" + assignment = "Concubus" + intern_alt_name = "Imp" + subdepartment_color = COLOR_BUBBLEGUM_RED + +// Quirk: Gargoyle +/datum/id_trim/quirk/gargoyle + trim_state = "trim_rectangle" + assignment = "Gargoyle" + intern_alt_name = "Statuette" + subdepartment_color = "#918E85" // Named "Stone Grey" + +// Quirk: Hallowed +/datum/id_trim/quirk/hallowed + trim_state = "trim_cleric" + assignment = "Deacon" + intern_alt_name = "Probationer" + subdepartment_color = LIGHT_COLOR_HOLY_MAGIC + threat_modifier = -1 // Equal to mind shield + +// Quirk: Rad Fiend +/datum/id_trim/quirk/rad_fiend + trim_state = "trim_rad" + assignment = "Rad Fiend" + intern_alt_name = "Radspawn" + subdepartment_color = "#14FF67" // Matches radiation outline + +// Quirk: Werewolf +/datum/id_trim/quirk/werewolf + trim_state = "trim_moon" + assignment = "Werewolf" + intern_alt_name = "Puppy" + subdepartment_color = "#F6F1D5" // Named "Moon Color" + threat_modifier = 2 // Equal to dress code violation diff --git a/modular_zzplurt/code/datums/quirks/negative_quirks/bad_touch.dm b/modular_zzplurt/code/datums/quirks/negative_quirks/bad_touch.dm new file mode 100644 index 0000000000000..7efcfe7cc237d --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/negative_quirks/bad_touch.dm @@ -0,0 +1,20 @@ +// Edit to update description +/datum/quirk/bad_touch + desc = "You don't like physical affection, and have a slight chance of retaliating against others who attempt it." + value = 0 + +/datum/quirk/bad_touch/add(client/client_source) + // Add status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/bad_touch) + +/datum/quirk/bad_touch/remove() + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/bad_touch) + +// Examine text status effect +/datum/status_effect/quirk_examine/bad_touch + id = QUIRK_EXAMINE_BADTOUCH + +// Set effect examine text +/datum/status_effect/quirk_examine/bad_touch/get_examine_text() + return span_warning("[owner.p_They()] look[owner.p_s()] like someone who doesn't enjoy physical affection.") diff --git a/modular_zzplurt/code/datums/quirks/negative_quirks/dumb_for_cum.dm b/modular_zzplurt/code/datums/quirks/negative_quirks/dumb_for_cum.dm new file mode 100644 index 0000000000000..083ccd84b6b27 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/negative_quirks/dumb_for_cum.dm @@ -0,0 +1,113 @@ +#define D4C_CRAVE_TIME 15 MINUTES + +/datum/quirk/dumb_for_cum + name = "Dumb For Cum" + desc = "You're totally addicted to seminal fluids. You need to consume them periodically, or face the consequences." + value = -2 + gain_text = span_purple("You feel an insatiable craving for seminal fluids.") + lose_text = span_purple("Cum didn't even taste that good, anyways.") + medical_record_text = "Patient seems to have an unhealthy psychological obsession with seminal fluids." + mob_trait = TRAIT_DUMB_CUM + icon = FA_ICON_FAUCET_DRIP + erp_quirk = TRUE + var/timer_crave + var/is_craving + +/datum/quirk/dumb_for_cum/add(client/client_source) + // Set timer + timer_crave = addtimer(CALLBACK(src, PROC_REF(crave)), D4C_CRAVE_TIME, TIMER_STOPPABLE) + + // Register special reagent interaction + RegisterSignal(quirk_holder, COMSIG_REAGENT_ADD_CUM, PROC_REF(handle_fluids)) + +/datum/quirk/dumb_for_cum/remove() + // Remove status trait + REMOVE_TRAIT(quirk_holder, TRAIT_DUMB_CUM_CRAVE, DUMB_CUM_TRAIT) + + // Remove penalty traits + REMOVE_TRAIT(quirk_holder, TRAIT_ILLITERATE, DUMB_CUM_TRAIT) + REMOVE_TRAIT(quirk_holder, TRAIT_DUMB, DUMB_CUM_TRAIT) + REMOVE_TRAIT(quirk_holder, TRAIT_PACIFISM, DUMB_CUM_TRAIT) + + // Remove mood event + quirk_holder.clear_mood_event(QMOOD_DUMB_CUM) + + // Remove timer + deltimer(timer_crave) + + // Unregister special reagent interaction + UnregisterSignal(quirk_holder, COMSIG_REAGENT_ADD_CUM) + +/// Proc to handle reagent interactions with bodily fluids +/datum/quirk/dumb_for_cum/proc/handle_fluids(datum/reagent/handled_reagent, amount) + SIGNAL_HANDLER + + // Check if currently craving + if(is_craving) + // Remove craving + uncrave() + +/datum/quirk/dumb_for_cum/proc/crave() + // Check if conscious + if(quirk_holder.stat == CONSCIOUS) + // Display emote + quirk_holder.try_lewd_autoemote("sigh") + + // Define list of phrases + var/list/trigger_phrases = list( + "Your stomach rumbles a bit and cum comes to your mind.",\ + "Urgh, you should really get some cum...",\ + "Some jizz wouldn't be so bad right now!",\ + "You're starting to long for some more cum..." + ) + // Alert user in chat + to_chat(quirk_holder, span_love("[pick(trigger_phrases)]")) + + // Add active status trait + ADD_TRAIT(quirk_holder, TRAIT_DUMB_CUM_CRAVE, DUMB_CUM_TRAIT) + + // Set craving variable + is_craving = TRUE + + // Add illiterate, dumb, and pacifist + ADD_TRAIT(quirk_holder, TRAIT_ILLITERATE, DUMB_CUM_TRAIT) + ADD_TRAIT(quirk_holder, TRAIT_DUMB, DUMB_CUM_TRAIT) + ADD_TRAIT(quirk_holder, TRAIT_PACIFISM, DUMB_CUM_TRAIT) + + // Add negative mood effect + quirk_holder.add_mood_event(QMOOD_DUMB_CUM, /datum/mood_event/cum_craving) + +/datum/quirk/dumb_for_cum/proc/uncrave() + // Remove active status trait + REMOVE_TRAIT(quirk_holder, TRAIT_DUMB_CUM_CRAVE, DUMB_CUM_TRAIT) + + // Set craving variable + is_craving = FALSE + + // Remove penalty traits + REMOVE_TRAIT(quirk_holder, TRAIT_ILLITERATE, DUMB_CUM_TRAIT) + REMOVE_TRAIT(quirk_holder, TRAIT_DUMB, DUMB_CUM_TRAIT) + REMOVE_TRAIT(quirk_holder, TRAIT_PACIFISM, DUMB_CUM_TRAIT) + + // Add positive mood event + quirk_holder.add_mood_event(QMOOD_DUMB_CUM, /datum/mood_event/cum_stuffed) + + // Remove timer + deltimer(timer_crave) + timer_crave = null + + // Add new timer + timer_crave = addtimer(CALLBACK(src, PROC_REF(crave)), D4C_CRAVE_TIME, TIMER_STOPPABLE) + +// Equal to 'decharged' mood event +/datum/mood_event/cum_craving + description = span_warning("I... NEED... CUM...") + mood_change = -10 + +// Equal to 'charged' mood event +/datum/mood_event/cum_stuffed + description = span_nicegreen("The cum feels so good inside me!") + mood_change = 8 + timeout = 5 MINUTES + +#undef D4C_CRAVE_TIME diff --git a/modular_zzplurt/code/datums/quirks/negative_quirks/flimsy.dm b/modular_zzplurt/code/datums/quirks/negative_quirks/flimsy.dm new file mode 100644 index 0000000000000..bcbadef997e97 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/negative_quirks/flimsy.dm @@ -0,0 +1,21 @@ +// REMOVED QUIRK - Use Fragility quirk instead +/datum/quirk/flimsy + name = "Flimsy" + desc = "Your body is a little more fragile then most, decreasing total health by 20%." + value = -4 + gain_text = span_danger("You feel like you could break with a single hit.") + lose_text = span_notice("You feel more durable.") + medical_record_text = "Patient has abnormally low capacity for injury." + mob_trait = TRAIT_FLIMSY + hardcore_value = 2 + icon = FA_ICON_USER_INJURED + hidden_quirk = TRUE + +/datum/quirk/flimsy/add(client/client_source) + quirk_holder.maxHealth *= 0.8 + +/datum/quirk/flimsy/remove() + if(!quirk_holder) + return + + quirk_holder.maxHealth *= 1.25 diff --git a/modular_zzplurt/code/datums/quirks/negative_quirks/gifted.dm b/modular_zzplurt/code/datums/quirks/negative_quirks/gifted.dm new file mode 100644 index 0000000000000..4662052f98cd0 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/negative_quirks/gifted.dm @@ -0,0 +1,4 @@ +// REMOVED QUIRK - Has no actual effect +/datum/quirk/gifted + value = 0 + hidden_quirk = TRUE diff --git a/modular_zzplurt/code/datums/quirks/negative_quirks/obese.dm b/modular_zzplurt/code/datums/quirks/negative_quirks/obese.dm new file mode 100644 index 0000000000000..d5c1e9abc8b02 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/negative_quirks/obese.dm @@ -0,0 +1,3 @@ +// Permanent version of Overweight quirk +/datum/quirk/obese + value = -4 diff --git a/modular_zzplurt/code/datums/quirks/negative_quirks/overweight.dm b/modular_zzplurt/code/datums/quirks/negative_quirks/overweight.dm new file mode 100644 index 0000000000000..49388b4cc15c6 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/negative_quirks/overweight.dm @@ -0,0 +1,22 @@ +// Re-implemented here to match old server +/datum/quirk/overweight + desc = "You're particularly fond of food, and join the shift being overweight." + value = 0 + gain_text = span_notice("You're feeling a bit chubby this shift!") + lose_text = span_notice("Your weight returns to an average level.") + mob_trait = TRAIT_OVERWEIGHT + +/datum/quirk/overweight/add(client/client_source) + // Set nutrition value + quirk_holder.nutrition = rand(NUTRITION_LEVEL_FAT + NUTRITION_LEVEL_START_MIN, NUTRITION_LEVEL_FAT + NUTRITION_LEVEL_START_MAX) + + // Set overeat duration + quirk_holder.overeatduration = 300 SECONDS + + // Add fat trait + ADD_TRAIT(quirk_holder, TRAIT_FAT, OBESITY) + +// Speed multiplier granted by this quirk +// Disabled because this is a neutral quirk +/datum/movespeed_modifier/overweight + multiplicative_slowdown = 0 // Previously 0.5 diff --git a/modular_zzplurt/code/datums/quirks/negative_quirks/thirsty.dm b/modular_zzplurt/code/datums/quirks/negative_quirks/thirsty.dm new file mode 100644 index 0000000000000..06280b9d62735 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/negative_quirks/thirsty.dm @@ -0,0 +1,33 @@ +// UNIMPLEMENTED QUIRK! +/datum/quirk/thirsty + name = "Thirsty" + desc = "You become thirsty twice as quickly. Make sure to drink plenty of fluids!" + value = 0 + gain_text = span_danger("You're beginning to feel parched again.") + lose_text = span_notice("Your elevated craving for water begins dying down.") + medical_record_text = "Patient's body is half as effective at retaining liquids, necessitating drinking twice as many liquids per day than usual for their species." + mob_trait = TRAIT_THIRSTY + hardcore_value = 1 + icon = FA_ICON_GLASS_WATER + mail_goodies = list ( + /obj/item/reagent_containers/cup/glass/waterbottle = 1 + ) + hidden_quirk = TRUE + +// Copy pasted from old code +// Thirst has not been implemented yet +/* +/datum/quirk/thirsty/add(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Set hunger multiplier + quirk_mob.physiology?.thirst_mod *= 2 + +/datum/quirk/thirsty/remove() + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Revert hunger multiplier + quirk_mob.physiology?.thirst_mod /= 2 +*/ diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/body_morpher.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/body_morpher.dm new file mode 100644 index 0000000000000..14ec7ba608dcd --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/body_morpher.dm @@ -0,0 +1,30 @@ +// REMOVED QUIRK - Disabled in favor of new NIFSoft method +/datum/quirk/body_morpher + name = "Body Morpher" + desc = "You have the ability to morph and shift your body, like a slimeperson can." + value = 0 + gain_text = span_notice("Your body feels more malleable.") + lose_text = span_notice("Your body returns to a normal consistency.") + medical_record_text = "Patient's body seems unusually malleable." + mob_trait = TRAIT_BODY_MORPHER + icon = FA_ICON_PEOPLE_ARROWS + mail_goodies = list ( + /obj/item/toy/foamblade = 1 // Fake changeling + ) + hidden_quirk = TRUE + +/datum/quirk/body_morpher/add(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Add quirk ability action datum + var/datum/action/innate/alter_form/quirk_action = new + quirk_action.Grant(quirk_mob) + +/datum/quirk/body_morpher/remove() + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove quirk ability action datum + var/datum/action/innate/alter_form/quirk_action = locate() in quirk_mob.actions + quirk_action.Remove(quirk_mob) diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/buns_of_steel.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/buns_of_steel.dm new file mode 100644 index 0000000000000..fa42894374c81 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/buns_of_steel.dm @@ -0,0 +1,13 @@ +// REMOVED QUIRK - Merged with Personal Space +/* +/datum/quirk/buns_of_steel + name = "Buns of Steel" + desc = "You are completely immune to all forms of ass slapping, and anyone who tries to slap your rock-hard ass usually gets a broken hand!" + value = 0 + gain_text = span_notice("Your ass rivals those of golems.") + lose_text = span_notice("Your butt feels more squishy and slappable.") + medical_record_text = "Patient's posterior displays incredible resilience." + mob_trait = TRAIT_STEEL_ASS + icon = FA_ICON_HAND + hidden_quirk = TRUE +*/ diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/choke_slut.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/choke_slut.dm new file mode 100644 index 0000000000000..cd0273c3722bc --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/choke_slut.dm @@ -0,0 +1,13 @@ +/datum/quirk/choke_slut + name = "Choke Slut" + desc = "You are aroused by suffocation." + value = 0 + gain_text = span_purple("You fantasize about feeling someone's fingers around your neck, choking you until you pass out or make a mess... Maybe both.") + lose_text = span_purple("You stop feeling aroused by suffocation.") + medical_record_text = "Patient exhibits an abnormal obsession with restricted breathing." + mob_trait = TRAIT_CHOKE_SLUT + icon = FA_ICON_HEAD_SIDE_COUGH + erp_quirk = TRUE + mail_goodies = list ( + /obj/item/reagent_containers/hypospray/medipen = 1 // Fix your oxy loss + ) diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/concubus.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/concubus.dm new file mode 100644 index 0000000000000..abef27c9f44ee --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/concubus.dm @@ -0,0 +1,69 @@ +// Original comments imply hunger changes are designed +// to encourage the quirk holder to seek a 'partner' +#define QUIRK_HUNGER_CONCUBUS 1.1 // 10% hungrier + +/datum/quirk/concubus + name = "Concubus" + desc = "Your seducer-like metabolism can only be sated by milk or semen. This also makes you slightly hungrier." + value = 0 + gain_text = span_purple("You feel a craving for certain bodily fluids.") + lose_text = span_purple("Your bodily fluid cravings fade back away.") + medical_record_text = "Patient claims to subsist entirely on lactose and reproductive fluids." + mob_trait = TRAIT_CONCUBUS + icon = FA_ICON_COW + erp_quirk = TRUE + mail_goodies = list ( + /datum/glass_style/drinking_glass/cum = 1, + /obj/item/reagent_containers/condiment/milk = 1 + ) + +/datum/quirk/concubus/add(client/client_source) + // Define quirk holder + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Check for valid holder + if(!istype(quirk_mob)) + return + + // Increase hunger rate + quirk_mob.physiology.hunger_mod *= QUIRK_HUNGER_CONCUBUS + + // Prevent consuming normal food + ADD_TRAIT(quirk_mob, TRAIT_LIVERLESS_METABOLISM, TRAIT_CONCUBUS) + //ADD_TRAIT(quirk_mob,TRAIT_NOTHIRST,QUIRK_TRAIT) // Thirst not yet implemented + + // Register special reagent interactions + RegisterSignals(quirk_holder, list(COMSIG_REAGENT_ADD_CUM, COMSIG_REAGENT_ADD_BREASTMILK), PROC_REF(handle_fluids)) + +/datum/quirk/concubus/remove() + // Define quirk holder + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Check for valid holder + if(!istype(quirk_mob)) + return + + // Revert hunger rate change + quirk_mob.physiology.hunger_mod /= QUIRK_HUNGER_CONCUBUS + + // Revert quirk traits + REMOVE_TRAIT(quirk_mob, TRAIT_LIVERLESS_METABOLISM, TRAIT_CONCUBUS) + //REMOVE_TRAIT(quirk_mob,TRAIT_NOTHIRST,QUIRK_TRAIT) // Thirst not yet implemented + + // Unregister special reagent interactions + UnregisterSignal(quirk_holder, COMSIG_REAGENT_ADD_CUM) + UnregisterSignal(quirk_holder, COMSIG_REAGENT_ADD_BREASTMILK) + +/// Proc to handle reagent interactions with bodily fluids +/datum/quirk/concubus/proc/handle_fluids(mob/living/target, datum/reagent/handled_reagent, amount) + SIGNAL_HANDLER + + // Check for valid reagent + if(ispath(handled_reagent)) + // Remove reagent + quirk_holder.reagents.remove_reagent(handled_reagent, amount) + + // Add Notriment + quirk_holder.reagents.add_reagent(/datum/reagent/consumable/notriment, amount) + +#undef QUIRK_HUNGER_CONCUBUS diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/cosglow.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/cosglow.dm new file mode 100644 index 0000000000000..b2f09357ad879 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/cosglow.dm @@ -0,0 +1,222 @@ +#define COSGLOW_OPACITY_MIN 32 +#define COSGLOW_OPACITY_MAX 128 +#define COSGLOW_OPACITY_DEFAULT 30 +#define COSGLOW_THICKNESS_MIN 1 +#define COSGLOW_THICKNESS_MAX 3 +#define COSGLOW_THICKNESS_DEFAULT 2 +#define COSGLOW_LAMP_RANGE_MIN 0 +#define COSGLOW_LAMP_RANGE_MAX 2 +#define COSGLOW_LAMP_RANGE_DEFAULT 1.5 +#define COSGLOW_LAMP_POWER_MIN 0.5 +#define COSGLOW_LAMP_POWER_MAX 1.5 +#define COSGLOW_LAMP_POWER_DEFAULT 1 +#define COSGLOW_LAMP_COLOR COLOR_WHITE + +// You might be an undercover agent. +/datum/quirk/cosglow + name = "Illuminated" + desc = "You emit a customizable soft glow! This isn't bright enough to replace your flashlight." + value = 0 + gain_text = span_notice("You feel empowered by a three-letter agency!") + lose_text = span_notice("You realize that working for the space agency sucks!") + medical_record_text = "Patient emits a subtle emissive aura." + mob_trait = TRAIT_COSGLOW + icon = FA_ICON_MAGIC_WAND_SPARKLES + mail_goodies = list ( + /obj/item/flashlight/glowstick = 1 + ) +/datum/quirk/cosglow/add(client/client_source) + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Add glow control action + var/datum/action/cosglow/update_glow/quirk_action = new + quirk_action.Grant(quirk_mob) + +/datum/quirk/cosglow/remove() + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove glow control action + var/datum/action/cosglow/update_glow/quirk_action = locate() in quirk_mob.actions + quirk_action.Remove(quirk_mob) + + // Remove glow effect + quirk_mob.remove_filter("rad_fiend_glow") + +// Light emitting status effect +/datum/status_effect/quirk_examine/cosglow + id = QUIRK_EXAMINE_COSGLOW + status_type = STATUS_EFFECT_REPLACE + + // Light effect object + var/obj/effect/dummy/lighting_obj/moblight/cosglow_light_obj + +/datum/status_effect/quirk_examine/cosglow/on_apply() + // Dynamic color is disabled + /* + // Get glow action + var/datum/action/cosglow/update_glow/quirk_action = locate() in owner.actions + + // Check if glow action exists + if(!quirk_action) + return FALSE + */ + + // Set light values + // Ignores range settings to prevent crew becoming lanterns + cosglow_light_obj = owner.mob_light(range = COSGLOW_LAMP_RANGE_DEFAULT, power = COSGLOW_LAMP_POWER_DEFAULT, color = COSGLOW_LAMP_COLOR) + + return TRUE + +/datum/status_effect/quirk_examine/cosglow/on_remove() + // Remove light + QDEL_NULL(cosglow_light_obj) + +/datum/status_effect/quirk_examine/cosglow/get_examine_text() + return span_notice("[owner.p_They()] emit[owner.p_s()] a harmless glowing aura.") + +// Glow actions +/datum/action/cosglow + name = "Broken Glow Action" + desc = "Report this to a coder." + button_icon = 'icons/obj/lighting.dmi' + button_icon_state = "slime-on" + check_flags = AB_CHECK_CONSCIOUS + +/datum/action/cosglow/update_glow + name = "Modify Glow" + desc = "Adjust your glow aura color and thickness." + + // Default glow color to use + // Analogous to radiation color + var/glow_color = "#14FF67" + + // Default thickness of glow outline + var/glow_thickness = COSGLOW_THICKNESS_DEFAULT + + // Default alpha of the glow outline + var/glow_opacity = COSGLOW_OPACITY_DEFAULT + + // Light range of the attached object + var/light_obj_power = COSGLOW_LAMP_POWER_DEFAULT + +/datum/action/cosglow/update_glow/Grant(mob/grant_to) + . = ..() + + // Define user mob + var/mob/living/carbon/human/action_mob = grant_to + + // Add outline effect + action_mob.add_filter("rad_fiend_glow", 1, outline_filter("color" = glow_color + "[glow_opacity]", "size" = glow_thickness)) + + // Define filter + var/filter = action_mob.get_filter("rad_fiend_glow") + + // Animate glow + animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1) + animate(alpha = 40, time = 2.5 SECONDS) + + // Apply status effect + action_mob.apply_status_effect(/datum/status_effect/quirk_examine/cosglow, TRAIT_COSGLOW) + +/datum/action/cosglow/update_glow/Remove(mob/remove_from) + . = ..() + + // Define user mob + var/mob/living/carbon/human/action_mob = remove_from + + // Remove glow + action_mob.remove_filter("rad_fiend_glow") + + // Remove status effect + action_mob.remove_status_effect(/datum/status_effect/quirk_examine/cosglow, TRAIT_COSGLOW) + +/datum/action/cosglow/update_glow/Trigger(trigger_flags) + . = ..() + + // Check parent return + if(!.) + return + + // Define user mob + var/mob/living/carbon/human/action_mob = owner + + // Ask user for color input + var/input_color = input(action_mob, "Select a color to use for your glow outline.", "Select Glow Color", glow_color) as color|null + + // Check if color input was given + // Reset to stored color when not given input + glow_color = (input_color ? input_color : glow_color) + + // Replaced by alert type input + /* + // Ask user for thickness input + var/input_thickness_tgui = tgui_input_number(action_mob, "How thick is your glow outline?", "Select Glow Thickness", default = COSGLOW_THICKNESS_DEFAULT, max_value = COSGLOW_THICKNESS_MAX, min_value = COSGLOW_THICKNESS_MIN) + + // Check if thickness input was given + // Reset to stored thickness when input is null + glow_thickness = isnull(input_thickness_tgui) ? glow_thickness : input_thickness_tgui + */ + + // Ask user for thickness input + switch(tgui_alert(action_mob, message = "How thick is your glow outline?", buttons = list("Light", "Regular", "Bold"))) + // Set based on input + if ("Light") + glow_thickness = COSGLOW_THICKNESS_MIN + light_obj_power = COSGLOW_LAMP_POWER_MIN + + if ("Regular") + glow_thickness = COSGLOW_THICKNESS_DEFAULT + light_obj_power = COSGLOW_LAMP_POWER_DEFAULT + + if ("Bold") + glow_thickness = COSGLOW_THICKNESS_MAX + light_obj_power = COSGLOW_LAMP_POWER_MAX + + // Opacity input interferes with the animation + /* + // Ask user for opacity input + // Limit maximum to prevent crew turning into stickers + var/input_opacity_tgui = tgui_input_number(action_mob, "How opaque is your glow outline?", "Select Glow Opacity", default = COSGLOW_OPACITY_DEFAULT, max_value = COSGLOW_OPACITY_MAX, min_value = COSGLOW_OPACITY_MIN) + + // Check if opacity input was given + // If no input is given, reset to stored opacity + var/opacity_clamped = isnull(input_opacity_tgui) ? hex2num(glow_opacity) : input_opacity_tgui + + // Update glow opacity + glow_opacity = num2hex(opacity_clamped, 2) + */ + + // Update outline effect + action_mob.add_filter("rad_fiend_glow", 1, outline_filter("color" = glow_color + "[glow_opacity]", "size" = glow_thickness)) + + // Define filter + var/filter = action_mob.get_filter("rad_fiend_glow") + + // Animate filter + animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1) + animate(alpha = 40, time = 2.5 SECONDS) + + // Find status effect + var/datum/status_effect/quirk_examine/cosglow/glow_effect = locate() in action_mob.status_effects + + // Update status effect light color + //glow_effect?.cosglow_light_obj?.set_light_color(glow_color) // Unused + + // Update status effect light range + glow_effect?.cosglow_light_obj?.set_light_power(light_obj_power) + +#undef COSGLOW_OPACITY_MIN +#undef COSGLOW_OPACITY_MAX +#undef COSGLOW_OPACITY_DEFAULT +#undef COSGLOW_THICKNESS_MIN +#undef COSGLOW_THICKNESS_MAX +#undef COSGLOW_THICKNESS_DEFAULT +#undef COSGLOW_LAMP_RANGE_MIN +#undef COSGLOW_LAMP_RANGE_MAX +#undef COSGLOW_LAMP_RANGE_DEFAULT +#undef COSGLOW_LAMP_POWER_MIN +#undef COSGLOW_LAMP_POWER_MAX +#undef COSGLOW_LAMP_POWER_DEFAULT +#undef COSGLOW_LAMP_COLOR diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/cum_plus.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/cum_plus.dm new file mode 100644 index 0000000000000..a177135b3f284 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/cum_plus.dm @@ -0,0 +1,68 @@ +#define CUMPLUS_MULT_GLOBAL 1.75 +#define CUMPLUS_MULT_TESTI 2 +#define CUMPLUS_MULT_VAGI 2 +#define CUMPLUS_MULT_BOOBI 2 + +/datum/quirk/cum_plus + name = "Extra-Productive Genitals" + desc = "Your genitals can hold twice the amount of fluid expected for their size, and produce slightly faster." + value = 0 + gain_text = span_purple("You feel pressure in your groin.") + lose_text = span_purple("You feel a weight lifted from your groin.") + medical_record_text = "Patient exhibits increased production of sexual fluids." + icon = FA_ICON_HEART_CIRCLE_PLUS + erp_quirk = TRUE + +// Note: Increasing size increases production rate +// Production scales based on size and mob arousal + +// Increase fluids +/datum/quirk/cum_plus/add(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Define potential fluid producing organ + // Check if it exists + // Multiply maximum fluid + + // Testicles + var/obj/item/organ/external/genital/testicles/mob_testi = quirk_mob.get_organ_slot(ORGAN_SLOT_TESTICLES) + if(mob_testi) + mob_testi.internal_fluid_maximum *= CUMPLUS_MULT_TESTI + + // Vagina + var/obj/item/organ/external/genital/vagina/mob_vagi = quirk_mob.get_organ_slot(ORGAN_SLOT_VAGINA) + if(mob_vagi) + mob_vagi.internal_fluid_maximum *= CUMPLUS_MULT_VAGI + + // Breasts + var/obj/item/organ/external/genital/testicles/mob_boobi = quirk_mob.get_organ_slot(ORGAN_SLOT_BREASTS) + if(mob_boobi) + mob_boobi.internal_fluid_maximum *= CUMPLUS_MULT_BOOBI + +// Reduce fluids +/datum/quirk/cum_plus/remove() + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Identical to above but divides + + // Testicles + var/obj/item/organ/external/genital/testicles/mob_testi = quirk_mob.get_organ_slot(ORGAN_SLOT_TESTICLES) + if(mob_testi) + mob_testi.internal_fluid_maximum /= CUMPLUS_MULT_TESTI + + // Vagina + var/obj/item/organ/external/genital/vagina/mob_vagi = quirk_mob.get_organ_slot(ORGAN_SLOT_VAGINA) + if(mob_vagi) + mob_vagi.internal_fluid_maximum /= CUMPLUS_MULT_VAGI + + // Breasts + var/obj/item/organ/external/genital/testicles/mob_boobi = quirk_mob.get_organ_slot(ORGAN_SLOT_BREASTS) + if(mob_boobi) + mob_boobi.internal_fluid_maximum /= CUMPLUS_MULT_BOOBI + +#undef CUMPLUS_MULT_GLOBAL +#undef CUMPLUS_MULT_TESTI +#undef CUMPLUS_MULT_VAGI +#undef CUMPLUS_MULT_BOOBI diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/cum_sniff.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/cum_sniff.dm new file mode 100644 index 0000000000000..673ab609c26d3 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/cum_sniff.dm @@ -0,0 +1,12 @@ +// UNIMPLEMENTED QUIRK! +/datum/quirk/cum_sniff + name = "Genital Sniffer" + desc = "You can accurately identify what reagents other people's genitals produce." + value = 0 + mob_trait = TRAIT_GFLUID_DETECT // TODO: Add the reagent detection system + gain_text = span_purple("You begin sensing peculiar smells from people's bits...") + lose_text = span_purple("People's genitals start smelling all the same to you...") + medical_record_text = "Patient has exemplary olfactory capability for specific body regions." + icon = FA_ICON_VIAL + erp_quirk = TRUE + hidden_quirk = TRUE diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/cursed_blood.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/cursed_blood.dm new file mode 100644 index 0000000000000..fa8e22a29823f --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/cursed_blood.dm @@ -0,0 +1,254 @@ +/** + * Phrases said in chat by the holder while "dreaming" + * These should be in first person + */ +#define CURSEDBLOOD_DREAM_PHRASES_SAY pick(\ + "[deity_name] please help me...",\ + "Unshackle me please...",\ + "[deity_name]... I've had enough of this dream...",\ + "Oh, [deity_name], please...",\ + "[deity_name], why have you forsaken me?",\ + "[deity_name], let my soul be free...",\ + "The fog hides everything, but it's coming for me...",\ + "I've walked this road too long...",\ + "[deity_name]... why do I still walk?",\ + "The night's cruel heart beats in mine...",\ + "In the city of beasts, I am prey...",\ + "[deity_name], where is the light in this place?",\ + "Beneath [deity_name]'s gaze, I lose my way...",\ + "[deity_name], why do I feel its presence?",\ + "The shadows will consume me whole",\ + "I've seen too much to be sane now",\ + "In dreams, I see the horrors that await",\ + ) + +/** + * Phrases said in chat by the holder while "dreaming" + * These should be in second person or have no subject + */ +#define CURSEDBLOOD_DREAM_PHRASES_HALLUCINATE pick(\ + "The night blocks all sight...",\ + "The moon is close. It will be a long hunt tonight.",\ + "The night is near its end...",\ + "Fear the blood...",\ + "A beast is at your door.",\ + "No light can save you now.",\ + "Find the lanterns in the dark...",\ + "Let the hunter become the hunted...",\ + "Beneath the moonlight, the darkness stirs...",\ + "Beyond the gates lies madness...",\ + "Unseen hands grasp for your soul..."\ + ) + +/// Potential burn damage taken while "dreaming" +#define CURSEDBLOOD_DREAM_DAMAGE_BURN 20 +/// Potential toxin damage taken while "dreaming" +#define CURSEDBLOOD_DREAM_DAMAGE_TOX 20 + +/// Chance of triggering dream phase 1: Speech +#define CURSEDBLOOD_DREAM_CHANCE_1 25 +/// Chance of triggering dream phase 2: Seisure +#define CURSEDBLOOD_DREAM_CHANCE_2 10 +/// Chance of triggering dream phase 3: Burning +#define CURSEDBLOOD_DREAM_CHANCE_3 25 + +/// Time to become unconscious during dream phase 2 +#define CURSEDBLOOD_DREAM_TIME_UNCONSCIOUS 5 SECONDS +/// Duration of the dream status effect when splashed +#define CURSEDBLOOD_DREAM_DURATION_SPLASH 10 SECONDS + +/datum/quirk/cursed_blood + name = "Cursed Blood" + desc = "Your lineage is afflicted with a blood-born curse. Avoid coming into contact with Holy Water. Hell Water, on the other hand..." + value = 0 + gain_text = span_notice("A curse from a land where men return as beasts runs deep in your blood.") + lose_text = span_notice("You feel the weight of the curse in your blood finally gone.") + medical_record_text = "Patient suffers from an unknown type of aversion to holy reagents. Keep them away from chaplains." + mob_trait = TRAIT_CURSED_BLOOD + hardcore_value = 1 + icon = FA_ICON_FIRE_FLAME_CURVED + mail_goodies = list ( + // This may be the only way to get Hell Water. + /obj/item/reagent_containers/cup/glass/bottle/holywater/hell = 1 + ) + /// Deity name to use in Dream messages + var/pref_deity + +/datum/quirk/cursed_blood/add(client/client_source) + // Register reagent interactions + RegisterSignal(quirk_holder, COMSIG_REAGENT_EXPOSE_HOLYWATER, PROC_REF(expose_holywater)) + RegisterSignal(quirk_holder, COMSIG_REAGENT_PROCESS_HELLWATER, PROC_REF(process_hellwater)) + RegisterSignals(quirk_holder, list(COMSIG_REAGENT_METABOLIZE_END_HOLYWATER,COMSIG_LIVING_DEATH), PROC_REF(end_holywater)) + + // Add profane penalties + quirk_holder.AddElementTrait(TRAIT_CHAPEL_WEAKNESS, TRAIT_CURSED_BLOOD, /datum/element/chapel_weakness) + quirk_holder.AddElementTrait(TRAIT_HOLYWATER_WEAKNESS, TRAIT_CURSED_BLOOD, /datum/element/holywater_weakness) + +/datum/quirk/cursed_blood/post_add() + // Try to read client preference + var/client_pref_deity = quirk_holder.client?.prefs?.read_preference(/datum/preference/name/deity) + + // Check if pref exists and is not the default value + if(client_pref_deity && (client_pref_deity != DEFAULT_DEITY)) + // Set new preferred deity + pref_deity = client_pref_deity + + // No preference set + else + // Pick a random crew member's name + var/datum/record/crew/random_crew = pick(GLOB.manifest.general) + + // Set deity to that name + pref_deity = random_crew?.name + +/datum/quirk/cursed_blood/remove() + // Unregister reagent interactions + UnregisterSignal(quirk_holder, COMSIG_REAGENT_EXPOSE_HOLYWATER) + UnregisterSignal(quirk_holder, COMSIG_REAGENT_PROCESS_HELLWATER) + + // Remove profane penalties + REMOVE_TRAIT(quirk_holder, TRAIT_CHAPEL_WEAKNESS, TRAIT_CURSED_BLOOD) + REMOVE_TRAIT(quirk_holder, TRAIT_HOLYWATER_WEAKNESS, TRAIT_CURSED_BLOOD) + +/// Called when exposed to Holy Water. Applies the dream status effect. +/datum/quirk/cursed_blood/proc/expose_holywater(mob/living/carbon/affected_mob, datum/reagent/handled_reagent, methods, reac_volume, show_message, touch_protection) + SIGNAL_HANDLER + + // Handled via status effect because .say cannot be used by a signal handler + + // Define if this effect should expire automatically + var/duration_override = FALSE + + // Check for splashing + if(methods & TOUCH) + // Set effect duration + duration_override = CURSEDBLOOD_DREAM_DURATION_SPLASH + + // Add status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_cursed_blood/dreaming, duration_override, pref_deity) + +/// Called when done metabolizing Holy Water or on death. Clears the dream status effect. +/datum/quirk/cursed_blood/proc/end_holywater() + SIGNAL_HANDLER + + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_cursed_blood/dreaming) + +/// Handle effects applied by consuming Hell Water +/datum/quirk/cursed_blood/proc/process_hellwater() + SIGNAL_HANDLER + + // Reduce disgust, hunger, and thirst + // These effects should match Hallowed bonus for Holy Water + quirk_holder.adjust_disgust(-2) + quirk_holder.adjust_nutrition(6) + //quirk_holder.adjust_thirst(6) + +// Status effect for Cursed Blood that applies Holy Water effects +/datum/status_effect/quirk_cursed_blood/dreaming + id = "cursed_blood_dream" + //duration = 10 SECONDS + tick_interval = 2 SECONDS + status_type = STATUS_EFFECT_REFRESH + alert_type = /atom/movable/screen/alert/status_effect/cursed_blood_dream + remove_on_fullheal = TRUE + + /// Default name of the target's religious deity + var/deity_name = DEFAULT_DEITY + +// Status effect condition checks +/datum/status_effect/quirk_cursed_blood/dreaming/on_creation(mob/living/new_owner, duration_override, pref_deity) + // Check for duration override + if(duration_override) + // Set limited duration + src.duration = duration_override + + // Check if owner is a blood cultist + if(IS_CULTIST(new_owner)) + // Set name to blood cultist god + deity_name = "Nar'Sie" + + // Check if owner is a clock cultist + else if(IS_CLOCK(new_owner)) + // Set name to clock cultist god + deity_name = "Ratvar" + + // Check disabled: Requires /carbon/human + /* + // Check if owner is a clown + else if(is_clown_job(new_owner.mind?.assigned_role)) + // Set name to clown god + deity_name = "Honkmother" + */ + + // Check if a global deity is set + else if(GLOB.deity) + // Set global deity name + deity_name = GLOB.deity + + // Check if a client preference was given + else if(pref_deity) + // Use preferred deity + deity_name = pref_deity + + // Run normally + return ..() + +// Status effect alert +/atom/movable/screen/alert/status_effect/cursed_blood_dream + name = "Endless Dream" + desc = "The night blocks all sight." + icon_state = "terrified" + alerttooltipstyle = "cult" + +// Processing dreaming status effect +/datum/status_effect/quirk_cursed_blood/dreaming/tick(seconds_between_ticks) + // Random chance to continue + if(!prob(CURSEDBLOOD_DREAM_CHANCE_1)) + return + + // Character speaks nonsense + owner.say(CURSEDBLOOD_DREAM_PHRASES_SAY, forced = "holy water", spans = list("cult_italic")) + + // Random chance to continue + if(!prob(CURSEDBLOOD_DREAM_CHANCE_2)) + return + + // Trigger a seisure + + // Alert in chat + owner.visible_message(span_danger("[owner] starts having a seizure!"), span_userdanger("You have a seizure!")) + + // Set unconscious + owner.Unconscious(CURSEDBLOOD_DREAM_TIME_UNCONSCIOUS) + + // Display messages + to_chat(owner, span_cult("[CURSEDBLOOD_DREAM_PHRASES_HALLUCINATE]")) + + // Add damage to queue + owner.adjustFireLoss(CURSEDBLOOD_DREAM_DAMAGE_BURN, updating_health = FALSE) + owner.adjustToxLoss(CURSEDBLOOD_DREAM_DAMAGE_TOX, updating_health = FALSE, forced = TRUE) + + // Update health + owner.updatehealth() + + // Random chance to continue + if(!prob(CURSEDBLOOD_DREAM_CHANCE_3)) + return + + // Ignite mob + owner.adjust_fire_stacks(2) + owner.ignite_mob() + +// Set status examine text +/datum/status_effect/quirk_cursed_blood/dreaming/get_examine_text() + return span_notice("[owner.p_They()] will never wake from this dream.") + +#undef CURSEDBLOOD_DREAM_PHRASES_SAY +#undef CURSEDBLOOD_DREAM_PHRASES_HALLUCINATE +#undef CURSEDBLOOD_DREAM_DAMAGE_BURN +#undef CURSEDBLOOD_DREAM_DAMAGE_TOX +#undef CURSEDBLOOD_DREAM_CHANCE_1 +#undef CURSEDBLOOD_DREAM_CHANCE_2 +#undef CURSEDBLOOD_DREAM_CHANCE_3 +#undef CURSEDBLOOD_DREAM_TIME_UNCONSCIOUS diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/excitable.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/excitable.dm new file mode 100644 index 0000000000000..b093d19346f01 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/excitable.dm @@ -0,0 +1,7 @@ +/datum/quirk/excitable/add(client/client_source) + . = ..() + quirk_holder.AddElement(/datum/element/pet_bonus/headpat) + +/datum/quirk/excitable/remove() + . = ..() + quirk_holder.RemoveElement(/datum/element/pet_bonus/headpat) diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/fluid_infuser.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/fluid_infuser.dm new file mode 100644 index 0000000000000..9809859da8912 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/fluid_infuser.dm @@ -0,0 +1,21 @@ +// REMOVED QUIRK - Disabled in favor of new NIFSoft method +/* +/datum/quirk/fluid_infuser + name = "Fluid Infuser" + desc = "You start the shift with one of NanoTrasen's new genital fluid inducers." + value = 0 + gain_text = span_notice("Your Fluid Infuser implant has been activated.") + lose_text = span_notice("Your Fluid Infuser implant encounters a critical error.") + medical_record_text = "Patient has been implanted with a Fluid Infuser implant." + // No mob trait + icon = FA_ICON_BOTTLE_DROPLET + erp_quirk = TRUE + hidden_quirk = TRUE +*/ +// Implant currently not implemented +/* +/datum/quirk/fluid_infuser/on_spawn() + . = ..() + var/obj/item/implant/genital_fluid/put_in = new + put_in.implant(quirk_holder, null, TRUE, TRUE) +*/ diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/gargoyle.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/gargoyle.dm new file mode 100644 index 0000000000000..8ccf39363500b --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/gargoyle.dm @@ -0,0 +1,318 @@ +// REMOVED QUIRK - Disabled until someone is willing to fix it +/* +/datum/quirk/gargoyle + name = "Gargoyle" + desc = "You are some form of gargoyle! You can only leave your stone form for so long, and will have to return to it to regain energy. On the bright side, you heal in statue form!" + value = 0 + quirk_flags = QUIRK_PROCESSES + gain_text = span_notice("You feel a strange longing to perch atop buildings.") + lose_text = span_notice("The stone curse leaves your body.") + medical_record_text = "Patient has a tenancy to solidify into stone." + mob_trait = TRAIT_GARGOYLE + icon = FA_ICON_MONUMENT + mail_goodies = list ( + /obj/item/chisel = 1 // Build yourself a friend + ) + var/energy = 0 + var/transformed = 0 + var/cooldown = 0 + var/paused = 0 + var/turf/position + var/obj/structure/statue/gargoyle/current = null + hidden_quirk = TRUE + +/datum/quirk/gargoyle/add(client/client_source) + var/mob/living/carbon/human/H = quirk_holder + if (!H) + return + var/datum/action/gargoyle/transform/T = new + var/datum/action/gargoyle/check/C = new + var/datum/action/gargoyle/pause/P = new + energy = 100 + cooldown = 30 + T.Grant(H) + C.Grant(H) + P.Grant(H) + +/datum/quirk/gargoyle/process(seconds_per_tick) + . = ..() + + var/mob/living/carbon/human/H = quirk_holder + + if (!H) + return + + if(paused && H.loc != position) + paused = 0 + energy -= 20 + + if(cooldown > 0) + cooldown-- + + if(!transformed && !paused && energy > 0) + energy -= 0.05 + + if(transformed) + var/integrity = current.get_integrity() + energy = min(energy + 0.3, 100) + if (H.getBruteLoss() > 0 || H.getFireLoss() > 0) + H.adjustBruteLoss(-0.5, forced = TRUE) + H.adjustFireLoss(-0.5, forced = TRUE) + else if (H.getOxyLoss() > 0 || H.getToxLoss() > 0) + H.adjustToxLoss(-0.3, forced = TRUE) + H.adjustOxyLoss(-0.5, forced = TRUE) //oxyloss heals by itself, doesn't need a nerfed heal + /*else if (H.getCloneLoss() > 0) + H.adjustCloneLoss(-0.3, forced = TRUE)*/ + else if (current && integrity < current.max_integrity) //health == maxHealth is true since we checked all damages above + current.update_integrity(min(integrity + 0.1, current.max_integrity)) + + if(!transformed && energy <= 0) + var/datum/action/gargoyle/transform/T = locate() in H.actions + if (!T) + T = new + T.Grant(H) + cooldown = 0 + T?.Trigger() + +/datum/quirk/gargoyle/remove() + var/mob/living/carbon/human/H = quirk_holder + if (!H) + return ..() + var/datum/action/gargoyle/transform/T = locate() in H.actions + var/datum/action/gargoyle/check/C = locate() in H.actions + var/datum/action/gargoyle/pause/P = locate() in H.actions + T?.Remove(H) + C?.Remove(H) + P?.Remove(H) + . = ..() + +/datum/action/gargoyle/transform + name = "Transform" + desc = "Transform into a statue, regaining energy in the process. Additionally, you will slowly heal while in statue form." + button_icon = 'icons/mob/actions/actions_changeling.dmi' + button_icon_state = "ling_camouflage" + var/obj/structure/statue/gargoyle/current = null + +/datum/action/gargoyle/transform/Trigger(trigger_flags) + . = ..() + + // Check parent return + if(!.) + return + + var/mob/living/carbon/human/H = owner + var/datum/quirk/gargoyle/T = H.get_quirk(/datum/quirk/gargoyle) + if(!T.cooldown) + if(!T.transformed) + if(!isturf(H.loc)) + return 0 + var/obj/structure/statue/gargoyle/S = new(H.loc, H) + S.name = "statue of [H.name]" + ADD_TRAIT(H, TRAIT_NOBLOOD, ACTION_TRAIT) + S.copy_overlays(H) + var/newcolor = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) + S.add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) + current = S + T.transformed = 1 + T.cooldown = 30 + T.paused = 0 + S.dir = H.dir + return 1 + else + qdel(current) + T.transformed = 0 + T.cooldown = 30 + T.paused = 0 + REMOVE_TRAIT(H, TRAIT_NOBLOOD, ACTION_TRAIT) + H.visible_message(span_warning("[H]'s skin rapidly softens, returning them to normal!"), span_userdanger("Your skin softens, freeing your movement once more!")) + else + to_chat(H, span_warning("You have transformed too recently; you cannot yet transform again!")) + return 0 + +/datum/action/gargoyle/check + name = "Check" + desc = "Check your current energy levels." + button_icon = 'modular_skyrat/modules/clock_cult/icons/actions_clock.dmi' + button_icon_state = "Linked Vanguard" + +/datum/action/gargoyle/check/Trigger(trigger_flags) + . = ..() + + // Check parent return + if(!.) + return + + var/mob/living/carbon/human/H = owner + var/datum/quirk/gargoyle/T = H.get_quirk(/datum/quirk/gargoyle) + to_chat(H, span_warning("You have [T.energy]/100 energy remaining!")) + +/datum/action/gargoyle/pause + name = "Preserve" + desc = "Become near-motionless, thusly conserving your energy until you move from your current tile. Note, you will lose a chunk of energy when you inevitably move from your current position, so you cannot abuse this!" + button_icon = 'modular_zzplurt/icons/mob/actions/actions_flightsuit.dmi' + button_icon_state = "flightsuit_lock" + +/datum/action/gargoyle/pause/Trigger(trigger_flags) + . = ..() + + // Check parent return + if(!.) + return + + var/mob/living/carbon/human/H = owner + var/datum/quirk/gargoyle/T = H.get_quirk(/datum/quirk/gargoyle) + + if(!T.paused) + T.paused = 1 + T.position = H.loc + to_chat(H, span_warning("You are now conserving your energy; this effect will end the moment you move from your current position!")) + return + else + to_chat(H, span_warning("You are already conserving your energy!")) + +/obj/structure/statue/gargoyle + name = "statue" + desc = "An incredibly intricate statue, which... almost seems alive!" + icon = 'modular_zzplurt/icons/obj/art/statue.dmi' + icon_state = "gargoyle" //empty sprite because it's supposed to copy over the overlays anyway, and otherwise, you end up with a white human male underlay beneath the statue. + density = TRUE + anchored = TRUE + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + max_integrity = 200 + var/mob/living/carbon/human/petrified_mob + var/old_max_health + var/old_size + var/was_tilted + var/was_lying + var/was_lying_prev + var/deconstructed = FALSE + +/obj/structure/statue/gargoyle/Initialize(mapload, mob/living/L) + . = ..() + var/mob/living/carbon/human/H = L + if(L) + petrified_mob = L + if(L.buckled) + L.buckled.unbuckle_mob(L,force=1) + L.visible_message(span_warning("[L]'s skin rapidly turns to stone!"), span_warning("Your skin abruptly hardens as you turn to stone once more!")) + dir = L.dir + transform = L.transform + pixel_x = L.pixel_x + pixel_y = L.pixel_y + layer = L.layer + var/datum/component/pixel_shift/comp = H.GetComponent(/datum/component/pixel_shift) + if(comp) + was_tilted = comp.how_tilted + was_lying = L.body_position == LYING_DOWN + was_lying_prev = L.lying_prev + L.forceMove(src) + // Add all Gargoyle traits as a list + L.add_traits(list( + TRAIT_MUTE, + TRAIT_IMMOBILIZED, + TRAIT_HANDS_BLOCKED, + //TRAIT_MOBILITY_NOUSE, + TRAIT_NOBREATH, + TRAIT_NOHUNGER, + //TRAIT_NOTHIRST, + TRAIT_NOFIRE, // Someone had issues with fire + TRAIT_GODMODE, + ), STATUE_MUTE) + L.click_intercept = src + L.faction |= FACTION_MIMIC //Stops mimics from instaqdeling people in statues + old_max_health = L.maxHealth + old_size = H.dna.features["body_size"] + atom_integrity = L.health + 100 //stoning damaged mobs will result in easier to shatter statues + max_integrity = atom_integrity + +/obj/structure/statue/gargoyle/proc/InterceptClickOn(mob/living/caller, params, atom/A) //technically could be bypassed by doing something that also uses click intercept but shrug + var/atom/movable/screen/movable/action_button/ab = A + if (istype(ab)) + if (istype(ab.linked_action, /datum/action/gargoyle)) + return FALSE + var/list/modifiers = params2list(params) + if (modifiers["shift"] && !modifiers["ctrl"] && !modifiers["middle"]) + return FALSE + return TRUE + +/obj/structure/statue/gargoyle/examine_more(mob/user) //something about the funny signals doesn't work here no matter how much I fucked around with it (ie registering to COMSIG_PARENT_EXAMINE_MORE doesn't do anything), so we have to overwrite the proc + . = ..() + if (petrified_mob) + SEND_SIGNAL(petrified_mob, COMSIG_ATOM_EXAMINE, user, .) + . -= span_notice("You examine [src] closer, but find nothing of interest...") + +/obj/structure/statue/gargoyle/Exited(atom/movable/gone, direction) + if(gone == petrified_mob) + petrified_mob = null + +/obj/structure/statue/gargoyle/Destroy() + if(petrified_mob) + petrified_mob.forceMove(loc) + // Remove all Gargoyle traits as a list + petrified_mob.remove_traits(list( + TRAIT_MUTE, + TRAIT_IMMOBILIZED, + TRAIT_HANDS_BLOCKED, + //TRAIT_MOBILITY_NOUSE, + TRAIT_NOBREATH, + TRAIT_NOHUNGER, + //TRAIT_NOTHIRST, + TRAIT_NOFIRE, // Someone had issues with fire + TRAIT_GODMODE, + ), STATUE_MUTE) + petrified_mob.faction -= FACTION_MIMIC + petrified_mob.click_intercept = null + petrified_mob.dir = dir + petrified_mob.dna.features["body_size"] = old_size + petrified_mob.dna.update_body_size() + var/damage = deconstructed ? petrified_mob.health : petrified_mob.health*(old_max_health/petrified_mob.maxHealth) - atom_integrity + 100 + petrified_mob.take_overall_damage(damage) //any new damage the statue incurred is transferred to the mob + petrified_mob.transform = transform + if(was_lying) + petrified_mob.set_body_position(LYING_DOWN) + petrified_mob.lying_prev = was_lying_prev + petrified_mob.pixel_x = pixel_x + petrified_mob.pixel_y = pixel_y + petrified_mob.layer = layer + var/datum/component/pixel_shift/comp = petrified_mob.GetComponent(/datum/component/pixel_shift) + if(comp) + comp.is_shifted = TRUE //just prevents bad things tbh + comp.how_tilted = was_tilted + if (pulledby && pulledby.pulling == src) //gotta checked just in case + INVOKE_ASYNC(pulledby, TYPE_PROC_REF(/atom/movable, start_pulling), petrified_mob, supress_message = TRUE) + var/datum/quirk/gargoyle/T = petrified_mob.get_quirk(/datum/quirk/gargoyle) + if (T) + T.transformed = 0 + petrified_mob = null + return ..() + +/obj/structure/statue/gargoyle/atom_deconstruct(disassembled) + . = ..() + + deconstructed = TRUE + visible_message(span_danger("[src] shatters!")) + qdel(src) + +/obj/structure/statue/gargoyle/attackby(obj/item/W, mob/living/user, params) + add_fingerprint(user) + //if(!(flags_1 & NODECONSTRUCT_1)) //Doesn't exist in this codebase? + if(default_unfasten_wrench(user, W)) + return + if(W.tool_behaviour == TOOL_WELDER) + if(!W.tool_start_check(user, amount=0)) + return FALSE + + if (petrified_mob && alert(user, "You are slicing apart a gargoyle! Are you sure you want to continue? This will severely harm them, if not outright kill them.",, "Continue", "Cancel") == "Cancel") + return + + user.visible_message(span_notice("[user] is slicing apart the [name]."), \ + span_notice("You are slicing apart the [name]...")) + if (petrified_mob) + to_chat(petrified_mob, span_userdanger("You are being sliced apart by [user]!")) + if(W.use_tool(src, user, 40, volume=50)) + user.visible_message(span_notice("[user] slices apart the [name]."), \ + span_notice("You slice apart the [name]!")) + deconstruct(TRUE) + return + return ..() +*/ diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/headpat_hater.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/headpat_hater.dm new file mode 100644 index 0000000000000..0287f0fb75841 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/headpat_hater.dm @@ -0,0 +1,49 @@ +// REMOVED QUIRK - This is now redundant with Bad Touch! +/* +/datum/quirk/headpat_hater + name = "Distant" + desc = "You don't seem to show much care for having your head touched. Others doing so won't make you wag your tail should you possess one, and the action may even attract your ire." + value = -1 + gain_text = span_danger("Others' touches begin to make your blood boil.") + lose_text = span_notice("Having your head pet doesn't sound so bad anymore.") + medical_record_text = "Patient cares little with or dislikes having their head touched." + mob_trait = TRAIT_DISTANT + icon = FA_ICON_HAND + hidden_quirk = TRUE + +/datum/quirk/headpat_hater/add(client/client_source) + // Add status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_headpat_hater) + +/datum/quirk/headpat_hater/remove() + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_headpat_hater) + +// Examine text status effect +/datum/status_effect/quirk_headpat_hater + id = "headpat_hater" + duration = -1 + alert_type = null + +// Set effect examine text +/datum/status_effect/quirk_headpat_hater/get_examine_text() + return span_warning("[owner.p_They()] look[owner.p_s()] like someone you shouldn't pat.") +*/ + +// Copy pasted from old code +/* +/datum/quirk/headpat_hater/add() + + var/mob/living/carbon/human/quirk_mob = quirk_holder + + var/datum/action/cooldown/toggle_distant/act_toggle = new + act_toggle.Grant(quirk_mob) + +/datum/quirk/headpat_hater/remove() + + var/mob/living/carbon/human/quirk_mob = quirk_holder + + var/datum/action/cooldown/toggle_distant/act_toggle = locate() in quirk_mob.actions + if(act_toggle) + act_toggle.Remove(quirk_mob) +*/ diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/headpat_slut.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/headpat_slut.dm new file mode 100644 index 0000000000000..6655389ec7a8e --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/headpat_slut.dm @@ -0,0 +1,57 @@ +/datum/quirk/headpat_slut + name = "Headpat Slut" + desc = "You love the feeling of others touching your head! Maybe a little too much. Others patting your head will provide a stronger mood boost, along with other sensual reactions." + value = 0 + gain_text = span_purple("You long for the wonderful sensation of head pats!") + lose_text = span_purple("Being pat on the head doesn't feel special anymore.") + medical_record_text = "Patient seems abnormally responsive to being touched on the head." + mob_trait = TRAIT_HEADPAT_SLUT + icon = FA_ICON_HAND_HOLDING_HEART + erp_quirk = TRUE + +/datum/quirk/headpat_slut/add(client/client_source) + // Add examine text status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/headpat_slut) + +/datum/quirk/headpat_slut/remove() + // Remove examine text status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/headpat_slut) + +// Examine text status effect +/datum/status_effect/quirk_examine/headpat_slut + id = QUIRK_EXAMINE_HEADPAT_SLUT + +// Set effect examine text +/datum/status_effect/quirk_examine/headpat_slut/get_examine_text() + return span_purple("[owner.p_Their()] head could use a good patting.") + +// Base mood event +// Based on 'betterhug' mood event +/datum/mood_event/headpat_slut + description = span_danger("I have an invalid mood event. I should report this.") + mood_change = 3 + timeout = 4 MINUTES + +// Mood for recipient +/datum/mood_event/headpat_slut/recipient + description = span_purple("I enjoyed receiving head pats.") + +/datum/mood_event/headpat_slut/recipient/add_effects(mob/giver) + // Check for valid giver + if(!giver) + return + + // Set dynamic text + description = span_purple("[giver.name] gives great head pats!") + +// Mood for giver +/datum/mood_event/headpat_slut/giver + description = span_purple("I enjoyed patting that person on the head.") + +/datum/mood_event/headpat_slut/giver/add_effects(mob/recipient) + // Check for valid recipient + if(!recipient) + return + + // Set dynamic text + description = span_purple("[recipient.name] was overjoyed by my touch!") diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/hypnotic_gaze.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/hypnotic_gaze.dm new file mode 100644 index 0000000000000..e674d356891ac --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/hypnotic_gaze.dm @@ -0,0 +1,418 @@ +#define HYPNOEYES_COOLDOWN_NORMAL 3 SECONDS +#define HYPNOEYES_COOLDOWN_BRAINWASH 30 SECONDS +#define HYPNOEYES_ACTION_TIME 5 SECONDS +#define HYPNOEYES_SLEEP_TIME 2 MINUTES +#define HYPNOEYES_DROWSINESS 40 + +// REMOVED QUIRK - Disabled in favor of new NIFSoft method +// This file still contains the action +/datum/quirk/hypnotic_gaze + name = "Hypnotic Gaze" + desc = "Prolonged eye contact with you can place a viewer into a highly-suggestible hypnotic trance." + value = 0 + gain_text = span_purple("Your eyes glimmer hypnotically...") + lose_text = span_purple("Your eyes return to normal.") + medical_record_text = "Patient's eyes exhibits soporific effects. Additional testing may be necessary." + mob_trait = TRAIT_HYPNOTIC_GAZE + icon = FA_ICON_FAN + erp_quirk = TRUE + hidden_quirk = TRUE + +/datum/quirk/hypnotic_gaze/add(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Add quirk ability action datum + var/datum/action/cooldown/hypnotize/act_hypno = new + act_hypno.Grant(quirk_mob) + + // Add status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/hypnotic_gaze) + +/datum/quirk/hypnotic_gaze/remove() + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove quirk ability action datum + var/datum/action/cooldown/hypnotize/act_hypno = locate() in quirk_mob.actions + act_hypno.Remove(quirk_mob) + + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/hypnotic_gaze) + +// Examine text status effect +/datum/status_effect/quirk_examine/hypnotic_gaze + id = QUIRK_EXAMINE_HYPNOTIC_GAZE + +// Set effect examine text +/datum/status_effect/quirk_examine/hypnotic_gaze/get_examine_text() + return span_purple("[owner.p_Their()] eyes glimmer with an entrancing power.") + +// +// Actions +// + +/datum/action/cooldown/hypnotize + name = "Hypnotize" + desc = "Stare deeply into someone's eyes, drawing them into a hypnotic slumber." + transparent_when_unavailable = TRUE + cooldown_time = HYPNOEYES_COOLDOWN_NORMAL + + // New icons + background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' + background_icon_state = "android" + button_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_nif.dmi' + button_icon_state = "hypnotize" + + // Should this create a brainwashed victim? + // Currently unused in this codebase + var/mode_brainwash = FALSE + + // Terminology used + var/term_hypno = "hypnotize" + var/term_suggest = "suggestion" + +// Upgraded variant +/datum/action/cooldown/hypnotize/brainwash + name = "Brainwash" + desc = "Stare deeply into someone's eyes, converting them into a loyal mind slave." + cooldown_time = HYPNOEYES_COOLDOWN_BRAINWASH + + // Should this create a brainwashed victim? + mode_brainwash = TRUE + + // Terminology used + term_hypno = "brainwash" + term_suggest = "command" + +/datum/action/cooldown/hypnotize/IsAvailable(feedback) + . = ..() + + // Check parent return + if(!.) + return FALSE + + // Check for carbon owner + if(!iscarbon(owner)) + // Warn user and return + to_chat(owner, span_warning("You shouldn't have this ability!")) + return FALSE + + // Define action owner + var/mob/living/carbon/human/action_owner = owner + + // Check if owner has eye protection + if(action_owner.get_eye_protection()) + // Warn the user, then return + to_chat(action_owner, span_warning("Your eyes need to be visible for this ability to work.")) + return FALSE + + // Define owner's eyes + var/obj/item/organ/internal/eyes/owner_eyes = owner.get_organ_slot(ORGAN_SLOT_EYES) + + // Check if eyes exist + if(!istype(owner_eyes)) + // Warn the user, then return + to_chat(action_owner, span_warning("You need eyes to use this ability!")) + return FALSE + + // Check if owner is blind + if(action_owner.is_blind()) + // Warn the user, then return + to_chat(action_owner, span_warning("Your blind [owner_eyes] are of no use.")) + return FALSE + + // All checks passed + return TRUE + +/datum/action/cooldown/hypnotize/proc/set_brainwash(set_to = FALSE) + // Check if state will change + if(mode_brainwash == set_to) + // Do nothing + return + + // Set new variable + mode_brainwash = set_to + + // Define toggle message + var/toggle_message = "experiences an error?" + + // Define log message type + var/log_message_type = "somehow screwed up" + + // Check if brainwashing + if(mode_brainwash) + // Set ability text + name = "Brainwash" + desc = "Stare deeply into someone's eyes, and force them to become your loyal slave." + + // Set cooldown time + cooldown_time = HYPNOEYES_COOLDOWN_BRAINWASH + + // Set terminology + term_hypno = "brainwash" + term_suggest = "command" + + // Set message suffix + toggle_message = "suddenly feels more intense!" + + // Set log message type + log_message_type = "GAINED" + + // Not brainwashing + else + // Set ability text + name = "Hypnotize" + desc = "Stare deeply into someone's eyes, drawing them into a hypnotic slumber." + + // Set cooldown time + cooldown_time = HYPNOEYES_COOLDOWN_NORMAL + + // Set terminology + term_hypno = "hypnotize" + term_suggest = "suggestion" + + // Set message suffix + toggle_message = "fades back to normal levels..." + + // Set log message type + log_message_type = "LOST" + + // Update buttons + owner.update_action_buttons() + + // Reset cooldown time + StartCooldown() + + // Alert user + to_chat(owner, span_warning("Your hypnotic power [toggle_message] You'll need time to adjust before using it again.")) + + // Log interaction + log_admin("[key_name(owner)] [log_message_type] hypnotic brainwashing powers.") + +/datum/action/cooldown/hypnotize/Activate() + // Define action owner + var/mob/living/carbon/human/action_owner = owner + + // Define target + var/grab_target = action_owner.pulling + + // Check for target + if(!grab_target) + // Warn the user, then return + to_chat(action_owner, span_warning("You need to grab someone first!")) + return + + // Check for cyborg + if(iscyborg(grab_target)) + // Warn the user, then return + to_chat(action_owner, span_warning("You can't [term_hypno] a cyborg!")) + return + + // Check for alien + // Taken from eyedropper check + if(isalien(grab_target)) + // Warn the user, then return + to_chat(action_owner, span_warning("[grab_target] does not seem to have any eyes!")) + return + + // Check for carbon human target + if(!ishuman(grab_target)) + // Warn the user, then return + to_chat(action_owner, span_warning("That's not a valid creature!")) + return + + // Check if target is alive + if(!isliving(grab_target)) + // Warn the user, then return + to_chat(action_owner, span_warning("You can't [term_hypno] the dead!")) + return + + // Check for aggressive grab + if(action_owner.grab_state < GRAB_AGGRESSIVE) + // Warn the user, then return + to_chat(action_owner, span_warning("You need a stronger grip before trying to [term_hypno] [grab_target]!")) + return + + // Define target + var/mob/living/carbon/human/action_target = grab_target + + // Check for sleep + if(action_target.IsSleeping()) + // Warn the user, then return + to_chat(action_owner, span_warning("You can't [term_hypno] [action_target] whilst [action_target.p_theyre()] asleep!")) + return + + // Check if target has a mind + if(!action_target.mind) + // Warn the user, then return + to_chat(action_owner, span_warning("[grab_target] doesn't have a compatible mind!")) + return + + /* Unused: Replaced by get_eye_protection + // Check if target's eyes are obscured + // ... by headwear + if((action_target.head && action_target.head.flags_cover & HEADCOVERSEYES)) + // Warn the user, then return + to_chat(action_owner, span_warning("[action_target]'s eyes are obscured by [action_target.head].")) + return + + // ... by a mask + else if((action_target.wear_mask && action_target.wear_mask.flags_cover & MASKCOVERSEYES)) + // Warn the user, then return + to_chat(action_owner, span_warning("[action_target]'s eyes are obscured by [action_target.wear_mask].")) + return + + // ... by glasses + else if((action_target.glasses && action_target.glasses.flags_cover & GLASSESCOVERSEYES)) + // Warn the user, then return + to_chat(action_owner, span_warning("[action_target]'s eyes are obscured by [action_target.glasses].")) + return + */ + + // Check if target has eye protection + if(action_target.get_eye_protection()) + // Warn the user, then return + to_chat(action_owner, span_warning("You have difficulty focusing on [action_target]'s eyes due to some form of eye protection, and are left unable to [term_hypno] [action_target.p_them()].")) + to_chat(action_target, span_notice("[action_owner] stares intensely at you, but stops after a moment.")) + return + + // Check if target is blind + if(action_target.is_blind()) + // Warn the user, then return + to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes, but see nothing but emptiness.")) + return + + // Check for mental anti-magic + // This should not apply to the Hallowed quirk! + if(action_target.can_block_magic(MAGIC_RESISTANCE_MIND)) + // Warn the users, then return + to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes. [action_target.p_They()] stare[action_target.p_s()] back at you as if nothing had happened.")) + to_chat(action_target, span_notice("[action_owner] stares intensely into your eyes for a moment. You sense nothing out of the ordinary from [action_owner.p_them()].")) + return + + // Check if client has denied hypnosis preference + if(READ_PREFS(action_target, choiced/erp_status_hypno) == "No") + // Warn the users, then return + to_chat(action_owner, span_warning("You sense that [action_target] is not comfortable with this type of interaction, and decide to respect [action_target.p_their()] preferences.")) + to_chat(action_target, span_notice("[action_owner] stares into your eyes with a strange conviction, but turns away after a moment.")) + return + + // Check for mindshield implant + if(HAS_TRAIT(action_target, TRAIT_MINDSHIELD)) + // Warn the users, then return + to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes, but hear a faint buzzing from [action_target.p_their()] head. It seems something is interfering.")) + to_chat(action_target, span_notice("[action_owner] stares intensely into your eyes for a moment, before a buzzing sound emits from your head.")) + return + + // Check if target mind is unconvertable + if(HAS_MIND_TRAIT(action_target, TRAIT_UNCONVERTABLE)) + // Warn the users, then return + to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes, but nothing happens. Your power cannot control [action_target.p_them()].")) + to_chat(action_target, span_notice("[action_owner] stares intensely into your eyes for a moment, then stops.")) + return + + // Check for sleep immunity + // This is required for SetSleeping to trigger + if(HAS_TRAIT(action_target, TRAIT_SLEEPIMMUNE) || !(action_target.status_flags & CANUNCONSCIOUS)) + // Warn the users, then return + to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes, and see nothing but unrelenting energy. You won't be able to subdue [action_target.p_them()] in this state!")) + to_chat(action_target, span_notice("[action_owner] stares intensely into your eyes, but sees something unusual about you...")) + return + + // Check for combat mode + if(action_target.combat_focus) + // Warn the users, then return + to_chat(action_owner, span_warning("[action_target] is acting too defensively! You'll need [action_target.p_them()] to lower [action_target.p_their()] guard first!")) + to_chat(action_target, span_notice("[action_owner] tries to stare into your eyes, but can't get a read on you.")) + return + + // Display chat messages + to_chat(action_owner, span_notice("You stare deeply into [action_target]'s eyes...")) + to_chat(action_target, span_warning("[action_owner] stares intensely into your eyes...")) + + // Try to perform action timer + if(!do_after(action_owner, HYPNOEYES_ACTION_TIME, action_target)) + // Action timer was interrupted + // Warn the user, then return + to_chat(action_owner, span_warning("You lose concentration on [action_target], and fail to [term_hypno] [action_target.p_them()]!")) + to_chat(action_target, span_notice("[action_owner]'s gaze is broken prematurely, freeing you from any potential effects.")) + return + + // Define blank response + var/input_consent + + // Check for non-consensual setting + if(READ_PREFS(action_target, choiced/erp_status_nc) != "Yes") + // Non-consensual is NOT enabled + // Define warning suffix + var/warning_target = (mode_brainwash ? "You will become a brainwashed victim, and be required to follow all orders given. [action_owner] accepts all responsibility for antagonistic orders." : "These are only suggestions, and you may disobey cases that strongly violate your character.") + + // Prompt target for consent response + input_consent = alert(action_target, "Will you fall into a hypnotic stupor? This will allow [action_owner] to issue hypnotic [term_suggest]s. [warning_target]", "Hypnosis", "Yes", "No") + + // When consent is denied + if(input_consent == "No") + // Warn the users, then return + to_chat(action_owner, span_warning("[action_target]'s attention breaks, despite the attempt to [term_hypno] [action_target.p_them()]! [action_target.p_they()] clearly don't want this!")) + to_chat(action_target, span_notice("Your concentration breaks as you realize you have no interest in following [action_owner]'s words!")) + return + + // Display local message + action_target.visible_message(span_warning("[action_target] falls into a deep slumber!"), span_danger("Your eyelids gently shut as you fall into a deep slumber. All you can hear is [action_owner]'s voice as you commit to following all of their [term_suggest]s.")) + + // Set sleeping + action_target.SetSleeping(HYPNOEYES_SLEEP_TIME) + + // Set drowsiness if below value + action_target.set_drowsiness_if_lower(HYPNOEYES_DROWSINESS) + + // Define warning suffix + var/warning_owner = (mode_brainwash ? "You are responsible for any antagonistic actions they take as a result of the brainwashing." : "This is only a suggestion, and [action_target.p_they()] may disobey if it violates [action_target.p_their()] character.") + + // Prompt action owner for response + var/input_suggestion = input("What [term_suggest] do you give [action_target]? Leave blank to release [action_target.p_them()] instead. [warning_owner]", "Hypnotic [term_suggest]", null, null) + + // Check if input text exists + if(!input_suggestion) + // Alert user of no input + to_chat(action_owner, "You decide not to give [action_target] a [term_suggest].") + + // Remove sleep, then return + action_target.SetSleeping(0) + return + + // Sanitize input text + input_suggestion = sanitize(input_suggestion) + + // Check if brainwash mode + if(mode_brainwash) + // Create brainwash objective + brainwash(action_target, input_suggestion) + + // Not in brainwash mode + else + // Display message to target + to_chat(action_target, span_reallybig(span_hypnophrase("...[input_suggestion]..."))) + + // Start cooldown + StartCooldown() + + // Display message to action owner + to_chat(action_owner, "You whisper your [term_suggest] in a smooth calming voice to [action_target].") + + // Play a sound effect + conditional_pref_sound(action_target, 'sound/effects/magic/smoke.ogg', 20, 1) + + // Display local message + action_target.visible_message(span_warning("[action_target] wakes up from their deep slumber!"), span_danger("Your eyelids gently open as you see [action_owner]'s face staring back at you.")) + + // Remove sleep, then return + action_target.SetSleeping(0) + return + +#undef HYPNOEYES_COOLDOWN_NORMAL +#undef HYPNOEYES_COOLDOWN_BRAINWASH +#undef HYPNOEYES_ACTION_TIME +#undef HYPNOEYES_SLEEP_TIME +#undef HYPNOEYES_DROWSINESS diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/jiggly_ass.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/jiggly_ass.dm new file mode 100644 index 0000000000000..faf270329b0e9 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/jiggly_ass.dm @@ -0,0 +1,43 @@ +/datum/quirk/jiggly_ass + name = "Buns of Thunder" + desc = "That pants-stretching, seat-creaking, undie-devouring butt of yours is as satisfying as it is difficult to keep balanced when smacked!" + value = 0 + quirk_flags = QUIRK_MOODLET_BASED + gain_text = span_purple("Your butt feels extremely smackable.") + lose_text = span_purple("Your butt no longer feels like it needs smacking.") + medical_record_text = "Patient is endowed with a superior posterior." + mob_trait = TRAIT_JIGGLY_ASS + icon = FA_ICON_CAKE + erp_quirk = TRUE + mail_goodies = list ( + /obj/item/food/cake = 1 // You know why + ) + COOLDOWN_DECLARE(wiggle_cooldown) + +/datum/quirk/jiggly_ass/add(client/client_source) + // Add status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/jiggly_ass) + +/datum/quirk/jiggly_ass/remove() + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/jiggly_ass) + +// Examine text status effect +/datum/status_effect/quirk_examine/jiggly_ass + id = QUIRK_EXAMINE_JIGGLY_ASS + +// Set effect examine text +/datum/status_effect/quirk_examine/jiggly_ass/get_examine_text() + return span_purple("[owner.p_Their()] butt could use a firm smack.") + +// Equal to 'pet animal' +/datum/mood_event/butt_slap + description = span_purple("Smacking that butt felt extremely satisfying!") + mood_change = 2 + timeout = 2 MINUTES + +// Equal to Well-Trained 'good boy' +/datum/mood_event/butt_slapped + description = span_purple("My jiggly butt was finally smacked, so satisfying!") + mood_change = 2 + timeout = 2 MINUTES diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/kiss_slut.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/kiss_slut.dm new file mode 100644 index 0000000000000..13e85422b0d0c --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/kiss_slut.dm @@ -0,0 +1,14 @@ +/datum/quirk/kiss_slut + name = "Kiss Slut" + desc = "The sheer thought of kissing someone makes you blush and overheat, sending you into a spiral of passion." + value = 0 + gain_text = span_purple("Thoughts of smooching invade your mind.") + lose_text = span_purple("You feel like your lips have had enough for now.") + medical_record_text = "Patient seems to demonstrate an extraordinary liking in kissing." + mob_trait = TRAIT_KISS_SLUT //No use for this yet + icon = FA_ICON_FACE_KISS_WINK_HEART + erp_quirk = TRUE + mail_goodies = list ( + /obj/item/lipstick/random = 20, + /obj/item/lipstick/hypnosyndie = 1 // Very small chance of ERP lipstick + ) diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/masked_mook.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/masked_mook.dm new file mode 100644 index 0000000000000..97f8f193bf931 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/masked_mook.dm @@ -0,0 +1,117 @@ +/datum/quirk/masked_mook + name = "Bane Syndrome" + desc = "You don't feel right without wearing some kind of gas mask." + value = 0 + quirk_flags = QUIRK_MOODLET_BASED + gain_text = span_danger("You start feeling the need to keep a gas mask on.") + lose_text = span_notice("You no longer feel the need to wear a gas mask.") + medical_record_text = "Patient feels a strong psychological attachment to gas masks." + mob_trait = TRAIT_MASKED_MOOK + hardcore_value = 1 + icon = FA_ICON_MASK_VENTILATOR + mail_goodies = list ( + /obj/item/gas_filter = 1 + ) + var/is_masked + +/datum/quirk/masked_mook/add(client/client_source) + // Register signal handlers + // Equip and unequip check for wearing the mask + // Stat change checks if the mask is adjusted + RegisterSignals(quirk_holder, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(check_outfit)) + +/datum/quirk/masked_mook/post_add() + // Evaluate outfit + check_outfit() + +/datum/quirk/masked_mook/proc/check_outfit() + SIGNAL_HANDLER + + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Define possible gas mask + var/obj/item/clothing/mask/gas/gasmask = quirk_mob.get_item_by_slot(ITEM_SLOT_MASK) + + // Check if wearing valid mask + if(quirk_mob.wear_mask && istype(gasmask)) + // Send positive mood event + quirk_mob.add_mood_event(QMOOD_MASKED_MOOK, /datum/mood_event/masked_mook/positive) + + // Remove old status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/masked_mook) + + // Apply positive status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/masked_mook/positive) + + // Check if already set + if(is_masked) + return + + // Alert user in chat + to_chat(quirk_mob, span_nicegreen("The mask makes you feel more complete.")) + + // Set mask status + is_masked = TRUE + + // Target is not wearing a gas mask + else + // Send negative mood event + quirk_mob.add_mood_event(QMOOD_MASKED_MOOK, /datum/mood_event/masked_mook/negative) + + // Remove old status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/masked_mook) + + // Apply negative status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/masked_mook/negative) + + // Check if already set + if(!is_masked) + return + + // Alert user in chat + to_chat(quirk_mob, span_warning("You start feeling incomplete without your mask...")) + + // Set mask status + is_masked = FALSE + +/datum/quirk/masked_mook/remove() + . = ..() + + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/masked_mook) + + // Remove mood event + quirk_holder.clear_mood_event(QMOOD_MASKED_MOOK) + + // Unregister signals + UnregisterSignal(quirk_holder, list( + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOB_UNEQUIPPED_ITEM + ) + ) + +// Item granter removed +// Please use the loadout system + +// Equal to having heirloom +/datum/mood_event/masked_mook/positive + description = span_nicegreen("I feel more complete with a gas mask on.") + mood_change = 1 + +// Equal to losing heirloom +/datum/mood_event/masked_mook/negative + description = span_warning("I feel incomplete without a gas mask...") + mood_change = -4 + +// Examine text status effect +/datum/status_effect/quirk_examine/masked_mook + id = QUIRK_EXAMINE_MASKED_MOOK + +// Set effect examine text - Positive +/datum/status_effect/quirk_examine/masked_mook/positive/get_examine_text() + return span_notice("[owner.p_They()] wear[owner.p_s()] [owner.p_their()] mask with a particular finesse.") + +// Set effect examine text - Negative +/datum/status_effect/quirk_examine/masked_mook/negative/get_examine_text() + return span_warning("[owner.p_They()] seem[owner.p_s()] uncomfortable with [owner.p_their()] unprotected face.") diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/messy.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/messy.dm new file mode 100644 index 0000000000000..dcd68645b844b --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/messy.dm @@ -0,0 +1,14 @@ +/datum/quirk/messy + name = "Messy" + desc = "You always manage to make a mess when you cum, even if it's not possible in normal circumstances." + value = 0 + gain_text = span_purple("You feel prepared to cover something in layer of bodily fluids.") + lose_text = span_purple("You don't feel the need to make a mess anymore.") + medical_record_text = "Patient's body has above-average fluid production capability." + mob_trait = TRAIT_MESSY + icon = FA_ICON_EXPLOSION + erp_quirk = TRUE + mail_goodies = list ( + /obj/item/mop = 1 // Clean this mess up! + ) + hidden_quirk = TRUE diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/modularlimbs.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/modularlimbs.dm new file mode 100644 index 0000000000000..f945a10a30d5d --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/modularlimbs.dm @@ -0,0 +1,188 @@ +#define MODLIMB_TIME_ADJUST 40 + +/datum/quirk/modularlimbs + name = "Modular Limbs" + desc = "You've undergone an experimental ligament hook surgery, allowing your limbs to be attached and detached easily. Unfortunately, this means everyone else can alter your limbs too!" + value = 0 + gain_text = span_notice("Your limbs feel like they could come off with a bit of effort.") + lose_text = span_notice("Your limbs feel more firmly attached.") + medical_record_text = "Patient has undergone an experimental ligament hook surgery." + mob_trait = TRAIT_MODULAR_LIMBS + icon = FA_ICON_PUZZLE_PIECE + +/datum/quirk/modularlimbs/add(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Add new quirk verb + add_verb(quirk_mob,/mob/living/proc/alterlimbs) + + // Add limb modification traits + quirk_mob.add_traits(list( + TRAIT_EASYDISMEMBER, + TRAIT_LIMBATTACHMENT, + ), TRAIT_MODULAR_LIMBS) + + // Self-amputation ability from Autotomy genetic + // Define quirk action + var/datum/action/cooldown/spell/self_amputation/modularlimbs/quirk_action = new + + // Grant quirk action + quirk_action.Grant(quirk_holder) + +/datum/quirk/modularlimbs/remove() + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove quirk verb + remove_verb(quirk_mob,/mob/living/proc/alterlimbs) + + // Remove limb modification traits + quirk_mob.remove_traits(list( + TRAIT_EASYDISMEMBER, + TRAIT_LIMBATTACHMENT, + ), TRAIT_MODULAR_LIMBS) + + // Define quirk action + var/datum/action/cooldown/spell/self_amputation/modularlimbs/quirk_action = new + + // Revoke quirk action + quirk_action.Remove(quirk_holder) + +// Variant of self amputation spell +/datum/action/cooldown/spell/self_amputation/modularlimbs + // More descriptive text, with warning + name = "Eject a random limb" + desc = "Violently eject a random limb from your body. This will hurt." + + // Default ability background + background_icon = 'icons/mob/actions/backgrounds.dmi' + background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND + overlay_icon = 'icons/mob/actions/backgrounds.dmi' + overlay_icon_state = null + +// New verb to alter limbs +/mob/living/proc/alterlimbs() + // Verb definitions + set name = "Alter Limbs" + set desc = "Remove or attach a limb!" + set category = "IC" + set src in view(usr.client) + + // Define mobs involved + var/mob/living/carbon/human/mob_initiator = usr + var/mob/living/carbon/human/mob_target = src + + // Check for self-interacting + // This causes a runtime error + if(mob_initiator == mob_target) + // Alert initiator and return + to_chat(mob_initiator, span_warning("You cannot alter your own limbs! Ask someone else for help.")) + return + + // Check if initiator is adjacent + if(!mob_target.Adjacent(mob_initiator)) + // Alert initiator and return + to_chat(mob_initiator, span_warning("You must be adjacent to [mob_target] to do this!")) + return + + // Note: Limb attachment is redundant with TRAIT_LIMBATTACHMENT + + // Define item held by initiator + var/obj/item/held_initiator = mob_initiator.get_active_held_item() + var/obj/item/bodypart/part_held = held_initiator + + // Check if held item is a body part + if(part_held) + // Check if a limb already exists + if(mob_target.get_bodypart(part_held.body_zone)) + // Alert initiator and return + to_chat(mob_initiator, span_warning("[mob_target.p_They()] already [mob_target.p_have()] a limb attached there!")) + return + + // Limb does not already exist! + // Alert target of interaction attempt + mob_target.visible_message(span_warning("[mob_initiator] is attempting to attach \the [part_held] onto [mob_target]!"), span_userdanger("[mob_initiator] is attempting to attach \the [part_held] to you!")) + + // Attempt interaction timer + if(do_after(mob_initiator, MODLIMB_TIME_ADJUST, target = mob_target)) + // Attempt to attach limb + part_held.try_attach_limb(mob_target) + + // Alert users and return + mob_target.visible_message(span_warning("[mob_initiator] successfully attaches \the [part_held] onto [mob_target]"), span_warning("[mob_initiator] has successfully attached \the [part_held.name] onto you; you can use that limb again!")) + return + + // Interaction timer failed + else + // Alert users and return + to_chat(mob_initiator, span_warning("You and [mob_target] must both stand still for you to alter their limbs!")) + return + + // Initiator is not holding a body part! + + // Check for valid target region + if(mob_initiator.zone_selected == BODY_ZONE_CHEST || mob_initiator.zone_selected == BODY_ZONE_HEAD) + // Alert initiator and return + to_chat(mob_initiator, span_warning("You must target either an arm or a leg!")) + return + if(mob_initiator.zone_selected == BODY_ZONE_PRECISE_GROIN || mob_initiator.zone_selected == BODY_ZONE_PRECISE_EYES || mob_initiator.zone_selected == BODY_ZONE_PRECISE_MOUTH) + // Alert initiator and return + to_chat(mob_initiator, span_warning("There is no limb here; select an arm or a leg!")) + return + + // Define target body part + var/obj/item/bodypart/part_target = mob_target.get_bodypart(mob_initiator.zone_selected) + + // Check if the limb exists to be removed + if(!part_target) + // Alert initiator and return + to_chat(mob_initiator, span_warning("[mob_target.p_They()] [mob_target.p_are()] already missing that limb!")) + return + + // Check if part is irremovable + if(part_target.bodypart_flags & BODYPART_UNREMOVABLE) + // Alert initiator and return + to_chat(mob_initiator, span_warning("[mob_target.p_Their()] limb is too firmly attached!")) + return + + // Region is valid and has a limb! + + // Define body part name + var/part_name = part_target.name + + // Alert users of interaction attempt + mob_target.visible_message(span_warning("[mob_initiator] is attempting to remove [mob_target]'s [part_name]!"), span_userdanger("[mob_initiator] is attempting to disconnect your [part_name]!")) + + // Attempt interaction timer + if(do_after(mob_initiator, MODLIMB_TIME_ADJUST, target = mob_target)) + // Define target again + var/obj/item/bodypart/part_target_check = mob_target.get_bodypart(mob_initiator.zone_selected) + + // Check if redefined target matches + if(part_target != part_target_check) + // Alert initiator and return + to_chat(mob_initiator, span_warning("You cannot target a different limb while already removing one!")) + return + + // Remove targeted limb + part_target_check.drop_limb() + + // Play sound effect + // Taken from admin panel + playsound(mob_target, 'sound/effects/cartoon_sfx/cartoon_pop.ogg', 70) + + // Update target's equipment + mob_target.update_equipment_speed_mods() + + // Alert users and return + mob_target.visible_message(span_warning("[mob_initiator] forcefully disconnects [mob_target]'s [part_name]!"), span_userdanger("[mob_initiator] has forcefully disconnected your [part_name]!")) + return + + // Interaction timer failed + else + // Alert users and return + to_chat(mob_initiator, span_warning("You and [mob_target] must both stand still for you to alter their limbs!")) + return + +#undef MODLIMB_TIME_ADJUST diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/nudist.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/nudist.dm new file mode 100644 index 0000000000000..cfa2f169968df --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/nudist.dm @@ -0,0 +1,114 @@ +/datum/quirk/nudist + name = "Nudist" + desc = "Wearing any clothing that covers your torso unnerves you. Bring a gear harness!" + value = 0 + quirk_flags = QUIRK_MOODLET_BASED + gain_text = span_danger("You feel spiritually connected to your natural form.") + lose_text = span_notice("It feels like clothing could fit you comfortably.") + medical_record_text = "Patient expresses a psychological need to remain unclothed." + mob_trait = TRAIT_NUDIST + icon = FA_ICON_LEAF + mail_goodies = list ( + // /datum/reagent/consumable/ethanol/panty_dropper = 1 // Not yet implemented + ) + var/is_nude + +/datum/quirk/nudist/add(client/client_source) + // Register signal handlers + RegisterSignals(quirk_holder, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(check_outfit)) + +/datum/quirk/nudist/remove() + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/nudist) + + // Unregister signals + UnregisterSignal(quirk_holder, list( + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOB_UNEQUIPPED_ITEM + ) + ) + +/datum/quirk/nudist/post_add() + // Evaluate outfit + check_outfit() + +/* Replaced by NIFSoft! +/datum/quirk/nudist/add_unique(client/client_source) + . = ..() + + // Spawn a Rapid Disrobe Implant + var/obj/item/implant/disrobe/quirk_implant = new + + // Implant into quirk holder + quirk_implant.implant(quirk_holder, null, TRUE, TRUE) +*/ + +/datum/quirk/nudist/proc/check_outfit() + SIGNAL_HANDLER + + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Check if torso is uncovered + if(quirk_mob.is_body_part_exposed(GROIN|CHEST)) + // Send positive mood event + quirk_mob.add_mood_event(QMOOD_NUDIST, /datum/mood_event/nudist_positive) + + // Remove old status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/nudist) + + // Apply positive status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/nudist/positive) + + // Check if already set + if(is_nude) + return + + // Alert user in chat + to_chat(quirk_mob, span_nicegreen("You begin to feel better without the restraint of clothing!")) + + // Set nude status + is_nude = TRUE + + // Torso is covered + else + // Send negative mood event + quirk_mob.add_mood_event(QMOOD_NUDIST, /datum/mood_event/nudist_negative) + + // Remove old status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/nudist) + + // Apply negative status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/nudist/negative) + + // Check if already set + if(!is_nude) + return + + // Alert user in chat + to_chat(quirk_mob, span_warning("The clothes feel wrong on your body...")) + + // Set nude status + is_nude = FALSE + +// Equal to having heirloom +/datum/mood_event/nudist_positive + description = span_nicegreen("I'm delighted to not be constricted by clothing.") + mood_change = 1 + +// Equal to losing heirloom +/datum/mood_event/nudist_negative + description = span_warning("I don't feel comfortable wearing this.") + mood_change = -4 + +// Examine text status effect +/datum/status_effect/quirk_examine/nudist + id = QUIRK_EXAMINE_NUDIST + +// Set effect examine text - Positive +/datum/status_effect/quirk_examine/nudist/positive/get_examine_text() + return span_notice("[owner.p_They()] appear[owner.p_s()] content with [owner.p_their()] lack of clothing.") + +// Set effect examine text - Negative +/datum/status_effect/quirk_examine/nudist/negative/get_examine_text() + return span_warning("[owner.p_They()] appear[owner.p_s()] disturbed by wearing clothing.") diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/personalspace.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/personalspace.dm new file mode 100644 index 0000000000000..1541111ed25c6 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/personalspace.dm @@ -0,0 +1,20 @@ +// Merges Buns of Steel flavor text +/datum/quirk/personalspace + desc = "You'd rather people keep their hands off your rear end. Anyone who tries to slap your rock-hard posterior usually gets a broken hand!" + medical_record_text = "Patient demonstrates negative reactions to their posterior being touched. Said posterior has developed a supernatural level of durability." + +/datum/quirk/personalspace/add(client/client_source) + // Add status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/personalspace) + +/datum/quirk/personalspace/remove() + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/personalspace) + +// Examine text status effect +/datum/status_effect/quirk_examine/personalspace + id = QUIRK_EXAMINE_PERSONALSPACE + +// Set effect examine text +/datum/status_effect/quirk_examine/personalspace/get_examine_text() + return span_warning("[owner.p_Their()] posterior is remarkably firm!") diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/phobia.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/phobia.dm new file mode 100644 index 0000000000000..7dcb87e2bedf5 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/phobia.dm @@ -0,0 +1,3 @@ +/datum/quirk/phobia + // Override icon + icon = FA_ICON_SPAGHETTI_MONSTER_FLYING diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/sodiumsensetivity.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/sodiumsensetivity.dm new file mode 100644 index 0000000000000..ef31f180083e9 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/sodiumsensetivity.dm @@ -0,0 +1,51 @@ +/// Maximum damage taken when splashed with salt +#define SALT_SENSITIVE_SPLASH_SALT_DAMAGE_CAP 20 + +/datum/quirk/sodium_sensetivity + name = "Sodium Sensitivity" + desc = "Your body is sensitive to sodium, and is burnt upon contact. Ingestion or contact with it is not advised." + value = -2 + gain_text = span_danger("You remember that advice about reducing your sodium intake.") + lose_text = span_notice("You remember how good salt makes things taste!") + medical_record_text = "Patient is highly allergic to to sodium, and should not come into contact with it under any circumstances." + mob_trait = TRAIT_SALT_SENSITIVE + hardcore_value = 1 + icon = FA_ICON_BIOHAZARD + +/datum/quirk/sodium_sensetivity/add(client/client_source) + // Register reagent interactions + RegisterSignal(quirk_holder, COMSIG_REAGENT_PROCESS_SALT, PROC_REF(process_salt)) + RegisterSignal(quirk_holder, COMSIG_REAGENT_EXPOSE_SALT, PROC_REF(expose_salt)) + +/datum/quirk/sodium_sensetivity/remove() + // Unregister reagent interactions + UnregisterSignal(quirk_holder, COMSIG_REAGENT_PROCESS_SALT) + UnregisterSignal(quirk_holder, COMSIG_REAGENT_EXPOSE_SALT) + +/// Handle effects applied when Salt is processed by the mob +/datum/quirk/sodium_sensetivity/proc/process_salt(mob/living/carbon/affected_mob, /datum/reagent/handled_reagent, seconds_per_tick, times_fired) + SIGNAL_HANDLER + + // Play burning sound + playsound(affected_mob, SFX_SEAR, 30, TRUE) + + // Do minor burn damage + affected_mob.adjustFireLoss(2 * REM * seconds_per_tick) + +/// Handle effects applied by being exposed to Salt +/datum/quirk/sodium_sensetivity/proc/expose_salt(mob/living/carbon/affected_mob, datum/reagent/handled_reagent, methods, reac_volume, show_message, touch_protection) + SIGNAL_HANDLER + + // Play burning sound + playsound(quirk_holder, SFX_SEAR, 30, TRUE) + + // Damage cap taken from bugkiller + // Intended to prevent instant crit from beaker splash + var/damage = min(round(0.4 * reac_volume, 0.1), SALT_SENSITIVE_SPLASH_SALT_DAMAGE_CAP) + if(damage < 1) + return + + // Cause burn damage based on amount + quirk_holder.adjustFireLoss(damage) + +#undef SALT_SENSITIVE_SPLASH_SALT_DAMAGE_CAP diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/storage_concealment.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/storage_concealment.dm new file mode 100644 index 0000000000000..8b68bf065334f --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/storage_concealment.dm @@ -0,0 +1,41 @@ +// REMOVED QUIRK - Disabled in favor of new NIFSoft method +/datum/quirk/storage_concealment + name = "Dorsualiphobic Augmentation" + desc = "You despise the idea of being seen wearing any type of back-mounted storage apparatus! A new technology shields you from the immense shame you may experience, by hiding your equipped backpack." + value = 0 + gain_text = span_notice("Your Chameleon Storage Concealment Implant has been activated.") + lose_text = span_notice("Your Chameleon Storage Concealment Implant encounters a critical error.") + medical_record_text = "Patient has exhibited concerns about being seen wearing a backpack." + icon = FA_ICON_BRIEFCASE + hidden_quirk = TRUE + + // UNUSED: Enable by setting these values to TRUE + // The shame is unbearable + //quirk_flags = /datum/quirk::quirk_flags | QUIRK_MOODLET_BASED | QUIRK_PROCESSES + +/datum/quirk/storage_concealment/add_unique(client/client_source) + // Create a new augment item + var/obj/item/implant/hide_backpack/put_in = new + + // Apply the augment to the quirk holder + put_in.implant(quirk_holder, null, TRUE, TRUE) + +/datum/quirk/storage_concealment/process(seconds_per_tick) + // This trait should only be applied by the augment + // Check the quirk holder for the trait + if(HAS_TRAIT(quirk_holder, TRAIT_HIDE_BACKPACK)) + // When found: Mood bonus + quirk_holder.add_mood_event(QMOOD_HIDE_BAG, /datum/mood_event/dorsualiphobic_mood_positive) + else + // When not found: Mood penalty + quirk_holder.add_mood_event(QMOOD_HIDE_BAG, /datum/mood_event/dorsualiphobic_mood_negative) + +// Equal to having heirloom +/datum/mood_event/dorsualiphobic_mood_positive + description = span_nicegreen("Nobody will know if I'm wearing a backpack or not.") + mood_change = 1 + +// Equal to losing heirloom +/datum/mood_event/dorsualiphobic_mood_negative + description = span_warning("I can't let anyone find out if I'm wearing a backpack or not!") + mood_change = -4 diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/undead.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/undead.dm new file mode 100644 index 0000000000000..37e7c9d89593b --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/undead.dm @@ -0,0 +1,79 @@ +#define QUIRK_HUNGER_UNDEAD 1.1 + +/datum/quirk/undead + name = "Undeath" + desc = "You stand on the border between life and death, causing you to function similar to a zombie." + value = 0 + gain_text = span_notice("The life has left your body, but you haven't stopped moving yet.") + lose_text = span_notice("By some miracle, you've been brought back to life!") + medical_record_text = "Patient is listed as deceased in medical records." + mob_trait = TRAIT_UNDEAD + icon = FA_ICON_HEAD_SIDE_VIRUS + +/datum/quirk/undead/add(client/client_source) + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Check if holder is inorganic + if(!(quirk_mob.mob_biotypes & MOB_ORGANIC)) + // Warn holder + to_chat(quirk_mob, span_warning("You have failed to become undead due to incompatible biology!")) + + // Do nothing + return FALSE + + // Replace biotypes + quirk_mob.mob_biotypes -= MOB_ORGANIC + quirk_mob.mob_biotypes += MOB_UNDEAD + + // Add undead traits + // Based on the High-Functioning Zombie species + // Powergamer traits are in separate quirks + quirk_mob.add_traits(list( + // SHARED WITH ALL ZOMBIES + TRAIT_EASILY_WOUNDED, + TRAIT_EASYDISMEMBER, + TRAIT_FAKEDEATH, + TRAIT_LIMBATTACHMENT, + TRAIT_LIVERLESS_METABOLISM, + TRAIT_NO_DNA_COPY, + TRAIT_NO_ZOMBIFY, + // HIGH FUNCTIONING UNIQUE + TRAIT_NOBLOOD + ), TRAIT_UNDEAD) + + // Add fake health HUD + quirk_holder.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, TRAIT_UNDEAD) + + // Increase hunger rate + //quirk_mob.physiology.hunger_mod *= QUIRK_HUNGER_UNDEAD + +/datum/quirk/undead/remove() + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Revert biotypes + quirk_mob.mob_biotypes += MOB_ORGANIC + quirk_mob.mob_biotypes -= MOB_UNDEAD + + // Remove undead traits + quirk_mob.remove_traits(list( + // SHARED WITH ALL ZOMBIES + TRAIT_EASILY_WOUNDED, + TRAIT_EASYDISMEMBER, + TRAIT_FAKEDEATH, + TRAIT_LIMBATTACHMENT, + TRAIT_LIVERLESS_METABOLISM, + TRAIT_NO_DNA_COPY, + TRAIT_NO_ZOMBIFY, + // HIGH FUNCTIONING UNIQUE + TRAIT_NOBLOOD + ), TRAIT_UNDEAD) + + // Remove fake health HUD + quirk_holder.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, TRAIT_UNDEAD) + + // Decrease hunger rate + //quirk_mob.physiology.hunger_mod /= QUIRK_HUNGER_UNDEAD + +#undef QUIRK_HUNGER_UNDEAD diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/werewolf.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/werewolf.dm new file mode 100644 index 0000000000000..074a6849b1ce8 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/werewolf.dm @@ -0,0 +1,241 @@ +// UNIMPLEMENTED QUIRK! +// Currently disabled due to appearance change issues +/datum/quirk/werewolf + name = "Werewolf" + desc = "A beastly affliction allows you to shape-shift into a large anthropomorphic canine at will." + value = 0 + gain_text = span_notice("You feel the full moon beckon.") + lose_text = span_notice("The moon's call hushes into silence.") + medical_record_text = "Patient has been reported howling at the night sky." + mob_trait = TRAIT_WEREWOLF + icon = FA_ICON_PAW + hidden_quirk = TRUE + +/datum/quirk/werewolf/add(client/client_source) + // Define quirk action + var/datum/action/cooldown/werewolf/transform/quirk_action = new + + // Grant quirk action + quirk_action.Grant(quirk_holder) + +/datum/quirk/werewolf/remove() + // Define quirk action + var/datum/action/cooldown/werewolf/transform/quirk_action = locate() in quirk_holder.actions + + // Revoke quirk action + quirk_action.Remove(quirk_holder) + +// +// Quirk Abilities +// + +/datum/action/cooldown/werewolf + name = "Lycanthrope Ability" + desc = "Do something related to werewolves." + button_icon = 'modular_zzplurt/icons/mob/actions/misc_actions.dmi' + button_icon_state = "Transform" + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_INCAPACITATED | AB_CHECK_CONSCIOUS + cooldown_time = 5 SECONDS + transparent_when_unavailable = TRUE + +/datum/action/cooldown/werewolf/transform + name = "Toggle Lycanthrope Form" + desc = "Transform in or out of your wolf form." + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED | AB_CHECK_PHASED + var/transformed = FALSE + var/species_changed = FALSE + var/werewolf_gender = "Lycan" + var/list/old_features + var/list/old_mutant_bodyparts + +/datum/action/cooldown/werewolf/transform/Grant() + . = ..() + + // Define action owner + var/mob/living/carbon/human/action_owner = owner + + // Define old mutant parts + old_mutant_bodyparts = action_owner.dna.mutant_bodyparts + + // Define old features + old_features = list("species" = SPECIES_HUMANOID, "legs" = "Plantigrade", "size" = 1, "bark") + + // Record features + old_features["species"] = action_owner.dna.species.type + old_features["custom_species"] = action_owner.dna.features["custom_species"] + //old_features["size"] = get_size(action_owner) // Temporarily disabled + old_features["bark"] = action_owner.blooper_id + old_features["taur"] = action_owner.dna.features["taur"] + + // Set species gendered name + switch(action_owner.gender) + if(MALE) + werewolf_gender = "Wer" + if(FEMALE) + werewolf_gender = "Wīf" + if(PLURAL) + werewolf_gender = "Hie" + if(NEUTER) + werewolf_gender = "Þing" + +/datum/action/cooldown/werewolf/transform/Activate() + // Define action owner + var/mob/living/carbon/human/action_owner = owner + + // Define genital organs + // Temporarily disabled + /* + var/obj/item/organ/external/genital/penis/organ_penis = action_owner.get_organ_slot(ORGAN_SLOT_PENIS) + // Add testicles here + var/obj/item/organ/external/genital/breasts/organ_breasts = action_owner.get_organ_slot(ORGAN_SLOT_BREASTS) + var/obj/item/organ/external/genital/vagina/organ_vagina = action_owner.get_organ_slot(ORGAN_SLOT_VAGINA) + */ + + // Play shake animation + action_owner.Shake(pixelshiftx = 0.2, pixelshifty = 0.2, duration = 1 SECONDS) + + // Transform into wolf form + if(!transformed) + // Species changing disabled due to issues + // This is heavily exploitable for bypassing species limitations + /* + // Define current species type + var/datum/species/owner_current_species = action_owner.dna.species.type + + // Check if species has changed + if(old_features["species"] != owner_current_species) + // Set old species + old_features["species"] = owner_current_species + + // Define species prefix + var/custom_species_prefix + + // Check if species is a template + if(ismammal(action_owner) || ishumanoid(action_owner)) + // Do nothing! + + // Check if species is already furry + else if(isvulpkanin(action_owner)) + // Do nothing + + // Check if species is a jelly subtype + else if(ispath(owner_current_species, /datum/species/jelly)) + // Set species prefix + custom_species_prefix = "Slime " + + // Species is not valid + else + // Change species + action_owner.set_species(/datum/species/mammal, 1) + + // Set species changed + species_changed = TRUE // TEMPORARILY DISABLED! + */ + + // Set species features + action_owner.dna.features["custom_species"] = "[werewolf_gender]wulf" + action_owner.dna.features["ears"] = "Wolf" // No "Jackal" ears + action_owner.dna.features["tail"] = "Otusian" + action_owner.dna.features["snout"] = "Sergal" + //action_owner.dna.features["legs"] = "Digitigrade" // Temporarily disabled + //action_owner.dna.features["fluff"] = "Plain" // No "Hyena" fluff + //action_owner.update_size(clamp(get_size(action_owner) + 0.5, RESIZE_MICRO, RESIZE_MACRO)) // Temporarily disabled + action_owner.set_blooper("Pugg") // No "bark" blooper + /* Temporarily disabled + if(old_features["taur"] != "None") + action_owner.dna.features["taur"] = "Canine" + if(!(action_owner.dna.species.species_traits.Find(DIGITIGRADE))) + action_owner.dna.species.species_traits += DIGITIGRADE + */ + + // Apply Appearance + //action_owner.regenerate_organs() // This causes ALL organs to be healed. Do not use it. + action_owner.update_body(TRUE) + action_owner.update_mutant_bodyparts(TRUE) + + // Update possible genital organs + /* Temporarily disabled + if(organ_breasts) + organ_breasts.color = "#[action_owner.dna.features["mcolor"]]" + organ_breasts.update() + if(organ_penis) + organ_penis.shape = "Knotted" + organ_penis.color = "#ff7c80" + organ_penis.update() + organ_penis.set_size(6) + // Add sheath + // Unimplemented + //alterer.dna.features["penis_sheath"] = new_sheath + //organ_penis.sheath = new_sheath + // Add testicles here + if(organ_vagina) + organ_vagina.shape = "Furred" + organ_vagina.color = "#[action_owner.dna.features["mcolor"]]" + organ_vagina.update() + */ + + // Un-transform from wolf form + else + // Check if species was changed + if(species_changed) + // Revert species + action_owner.set_species(old_features["species"], TRUE) + + // Clear species changed flag + species_changed = FALSE + + // Revert species trait + action_owner.set_blooper(old_features["bark"]) + action_owner.dna.features["custom_species"] = old_features["custom_species"] + action_owner.dna.features["ears"] = old_features["ears"] + action_owner.dna.features["snout"] = old_features["snout"] + action_owner.dna.features["tail"] = old_features["tail"] + action_owner.dna.features["legs"] = old_features["legs"] + //action_owner.dna.features["fluff"] = old_features["fluff"] // Temporarily disabled + /* Temporarily disabled + if(old_features["taur"] != "None") + action_owner.dna.features["taur"] = old_features["taur"] + if(old_features["legs"] == "Plantigrade") + action_owner.dna.species.species_traits -= DIGITIGRADE + action_owner.Digitigrade_Leg_Swap(TRUE) + action_owner.dna.species.mutant_bodyparts["legs"] = old_features["legs"] + */ + + // Apply Appearance + //action_owner.regenerate_organs() // This causes ALL organs to be healed. Do not use it. + action_owner.update_body(TRUE) + action_owner.update_mutant_bodyparts(TRUE) + //action_owner.update_size(clamp(get_size(action_owner) - 0.5, RESIZE_MICRO, RESIZE_MACRO)) // Temporarily disabled + + // Revert genital organs + /* Temporarily disabled + if(organ_breasts) + organ_breasts.color = "#[old_features["breasts_color"]]" + organ_breasts.update() + if(action_owner.has_penis()) + organ_penis.shape = old_features["cock_shape"] + organ_penis.color = "#[old_features["cock_color"]]" + organ_penis.update() + //organ_penis.modify_size(-6) // Temporarily disabled + if(action_owner.has_vagina()) + organ_vagina.shape = old_features["vag_shape"] + organ_vagina.color = "#[old_features["vag_color"]]" + organ_vagina.update() + organ_vagina.update_size() + */ + + // Set transformation message + var/owner_p_their = action_owner.p_their() + var/toggle_message = (!transformed ? "[action_owner] shivers, [owner_p_their] flesh bursting with a sudden growth of thick fur as [owner_p_their] features contort to that of a beast, fully transforming [action_owner.p_them()] into a [werewolf_gender]wulf!" : "[action_owner] shrinks, [owner_p_their] wolfish features quickly receding.") + + // Alert in local chat + action_owner.visible_message(span_danger(toggle_message)) + + // Toggle transformation state + transformed = !transformed + + // Start cooldown + StartCooldown() + + // Return success + return TRUE diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/arachnid.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/arachnid.dm new file mode 100644 index 0000000000000..862a8d5d56da7 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/arachnid.dm @@ -0,0 +1,65 @@ +/datum/quirk/arachnid + name = "Silkspinner" + desc = "Your bodily anatomy allows you to spin webs and cocoons, even though you aren't an arachnid! This quirk does nothing for members of the arachnid species." + value = 2 + medical_record_text = "Patient has attempted to cover the room in webs, claiming to be \"making a nest\"." + mob_trait = TRAIT_ARACHNID + gain_text = span_notice("You feel a strange sensation near your anus...") + lose_text = span_notice("You feel like you can't spin webs anymore...") + icon = FA_ICON_SPIDER + +/datum/quirk/arachnid/add(client/client_source) + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Check if already an arachnid + if(is_species(quirk_mob,/datum/species/arachnid)) + // Warn user and return + to_chat(quirk_mob, span_warning("As an arachnid, the Arachnid quirk does nothing for you! These abilities are innate to your species.")) + return + + // Define arachnid abilities + var/datum/action/innate/arachnid/spin_web/ability_web = new + var/datum/action/innate/arachnid/spin_cocoon/ability_cocoon = new + + // Grant abilities + ability_web.Grant(quirk_mob) + ability_cocoon.Grant(quirk_mob) + + // Grant arachnid traits + quirk_mob.add_traits(list( + TRAIT_WEB_SURFER, + TRAIT_WEB_WEAVER, + ), TRAIT_ARACHNID) + + // Check if mob was already a bug + if(!(quirk_mob.mob_biotypes & MOB_BUG)) + // Add bug biotype + quirk_mob.mob_biotypes += MOB_BUG + +/datum/quirk/arachnid/remove() + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Check if already an arachnid + if(is_species(quirk_mob,/datum/species/arachnid)) + return + + // Define arachnid abilities + var/datum/action/innate/arachnid/spin_web/ability_web = locate(/datum/action/innate/arachnid/spin_web) in quirk_mob.actions + var/datum/action/innate/arachnid/spin_cocoon/ability_cocoon = locate(/datum/action/innate/arachnid/spin_cocoon) in quirk_mob.actions + + // Revoke abilities + ability_web?.Remove(quirk_mob) + ability_cocoon?.Remove(quirk_mob) + + // Revoke arachnid traits + quirk_mob.remove_traits(list( + TRAIT_WEB_SURFER, + TRAIT_WEB_WEAVER, + ), TRAIT_ARACHNID) + + // Check if species should still be a bug + if(!(quirk_mob.dna?.species?.inherent_biotypes & MOB_BUG)) + // Remove bug biotype + quirk_mob.mob_biotypes -= MOB_BUG diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/ash_resistance.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/ash_resistance.dm new file mode 100644 index 0000000000000..325206d793fe3 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/ash_resistance.dm @@ -0,0 +1,23 @@ +/datum/quirk/ash_resistance + name = "Ashen Resistance" + desc = "Your body is adapted to the burning sheets of ash that coat volcanic worlds. The heavy downpours of silt will still tire you." + value = 2 + gain_text = span_notice("Your body has adapted to the harsh climates of volcanic worlds.") + lose_text = span_notice("Your body has lost its ashen adaptations.") + medical_record_text = "Patient has an abnormally thick epidermis." + mob_trait = TRAIT_ASHRESISTANCE + hardcore_value = -1 + icon = FA_ICON_FIRE_FLAME_SIMPLE + +// Without the stamina penalty, this quirk can replace SEVA suits +// Please increase the cost if you use the code below + +/* +/datum/quirk/ashresistance/add() + // Add ash storm immunity + ADD_TRAIT(quirk_holder, TRAIT_ASHSTORM_IMMUNE, TRAIT_ASHRESISTANCE) + +/datum/quirk/ashresistance/remove() + // Remove ash storm immunity + REMOVE_TRAIT(quirk_holder, TRAIT_ASHSTORM_IMMUNE, TRAIT_ASHRESISTANCE) +*/ diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/bloodfledge.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/bloodfledge.dm new file mode 100644 index 0000000000000..43212e5470126 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/bloodfledge.dm @@ -0,0 +1,1608 @@ +/// Amount of blood taken from a target on bite +#define BLOODFLEDGE_DRAIN_AMT 50 +/// Base amount of time to bite a target before adjustments +#define BLOODFLEDGE_DRAIN_TIME 50 +/// Cooldown for the bite ability +#define BLOODFLEDGE_COOLDOWN_BITE 60 // Six seconds +/// Cooldown for the revive ability +#define BLOODFLEDGE_COOLDOWN_REVIVE 3000 // Five minutes +/// How much blood can be held after biting +#define BLOODFLEDGE_BANK_CAPACITY (BLOODFLEDGE_DRAIN_AMT * 2) +/// How much damage is healed in a coffin +#define BLOODFLEDGE_HEAL_AMT -2 + +/datum/quirk/item_quirk/bloodfledge + name = "Bloodfledge" + desc = "You are apprentice sanguine sorcerer endowed with vampiric power beyond that of a common hemophage. While not truly undead, many of the same conditions still apply." + value = 4 + gain_text = span_notice("A sanguine blessing flows through your body, granting it new strength.") + lose_text = span_notice("The sanguine blessing fades away...") + medical_record_text = "Patient appears to possess a paranormal connection to otherworldly forces." + mob_trait = TRAIT_BLOODFLEDGE + hardcore_value = -2 + icon = FA_ICON_CHAMPAGNE_GLASSES + /// Toggle between using blood volume or nutrition. Blood volume is used for hemophages. + var/use_nutrition = TRUE + +/datum/quirk/item_quirk/bloodfledge/add(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Register examine text + RegisterSignal(quirk_holder, COMSIG_ATOM_EXAMINE, PROC_REF(quirk_examine_bloodfledge)) + + // Register wooden stake interaction + RegisterSignal(quirk_holder, COMSIG_MOB_STAKED, PROC_REF(on_staked)) + + // Add quirk language + quirk_mob.grant_language(/datum/language/vampiric, ALL, LANGUAGE_QUIRK) + + /** + * Hemophage check + * + * Check if the quirk holder is a hemophage. + * Ignore remaining features if they are + */ + if(ishemophage(quirk_mob)) + return + + // Add quirk traits + ADD_TRAIT(quirk_mob, TRAIT_LIVERLESS_METABOLISM, TRAIT_BLOODFLEDGE) + //ADD_TRAIT(quirk_mob, TRAIT_NOTHIRST, TRAIT_BLOODFLEDGE) // Not yet implemented + + // Register blood consumption interaction + RegisterSignal(quirk_holder, COMSIG_REAGENT_ADD_BLOOD, PROC_REF(on_consume_blood)) + + // Register coffin interaction + RegisterSignal(quirk_holder, COMSIG_ENTER_COFFIN, PROC_REF(on_enter_coffin)) + + // Set skin tone, if possible + if(HAS_TRAIT(quirk_mob, TRAIT_USES_SKINTONES) && !(quirk_mob.skin_tone != initial(quirk_mob.skin_tone))) + quirk_mob.skin_tone = "albino" + quirk_mob.dna.update_ui_block(DNA_SKIN_TONE_BLOCK) + + // Add vampiric biotype + quirk_mob.mob_biotypes |= MOB_VAMPIRIC + + // Add profane penalties + quirk_holder.AddElementTrait(TRAIT_CHAPEL_WEAKNESS, TRAIT_BLOODFLEDGE, /datum/element/chapel_weakness) + quirk_holder.AddElementTrait(TRAIT_HOLYWATER_WEAKNESS, TRAIT_BLOODFLEDGE, /datum/element/holywater_weakness) + +/datum/quirk/item_quirk/bloodfledge/post_add() + . = ..() + + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Teach how to make the Hemorrhagic Sanguinizer + quirk_mob.mind?.teach_crafting_recipe(/datum/crafting_recipe/emag_bloodfledge) + + // Check for non-organic mob + // Robotic and other mobs have technical issues with adjusting damage + if(!(quirk_mob.mob_biotypes & MOB_ORGANIC)) + // Warn user + to_chat(quirk_mob, span_warning("As a non-organic lifeform, your structure is only able to support limited sanguine abilities! Regeneration and revival are not possible.")) + + // User is organic + else + // Define and grant ability Revive + var/datum/action/cooldown/bloodfledge/revive/act_revive = new + act_revive.Grant(quirk_mob) + + /** + * Hemophage check + * + * Check if the quirk holder is a hemophage. + * Ignore remaining features if they are + */ + if(ishemophage(quirk_mob)) + // Warn user + to_chat(quirk_mob, span_warning("Because you already possess the tumor's corruption, some redundant bloodfledge abilities remain dormant. Your bite ability will manifest once the tumor's corruption takes hold.")) + + // Disable nutrition mode + use_nutrition = FALSE + + // Ignore remaining features + return + + // Define owner tongue + var/obj/item/organ/internal/tongue/target_tongue = quirk_holder.get_organ_slot(ORGAN_SLOT_TONGUE) + + // Check if tongue exists + if(target_tongue) + // Force preference for bloody food + target_tongue.disliked_foodtypes &= ~BLOODY + target_tongue.liked_foodtypes |= BLOODY + + // Define and grant ability Bite + var/datum/action/cooldown/bloodfledge/bite/act_bite = new + act_bite.Grant(quirk_mob) + +// Processing is currently only used for coffin healing +/datum/quirk/item_quirk/bloodfledge/process(seconds_per_tick) + // Define potential coffin + var/quirk_coffin = quirk_holder.loc + + // Check if the current area is a coffin + if(!istype(quirk_coffin, /obj/structure/closet/crate/coffin)) + // Warn user + to_chat(quirk_holder, span_warning("Your connection to the other-world is broken upon leaving the coffin!")) + + // Stop processing and return + STOP_PROCESSING(SSquirks, src) + return + + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Quirk mob must be injured + if(quirk_mob.health >= quirk_mob.maxHealth) + // Warn user + to_chat(quirk_mob, span_notice("[quirk_coffin] does nothing more to help you, as your body is fully mended.")) + + // Stop processing and return + STOP_PROCESSING(SSquirks, src) + return + + /// Define if nutrition or blood volume is sufficient. Will stop healing if FALSE. + var/has_enough_blood = TRUE + + // Check if using nutrition mode + if(use_nutrition) + // Nutrition level must be above STARVING + if(quirk_mob.nutrition <= NUTRITION_LEVEL_STARVING) + // Set variable + has_enough_blood = FALSE + + // Using blood volume mode + else + // Blood level must be above SURVIVE + if(quirk_mob.blood_volume <= BLOOD_VOLUME_SURVIVE) + // Set variable + has_enough_blood = FALSE + + // Check if nutrition or blood volume is high enough + if(!has_enough_blood) + // Warn user + to_chat(quirk_mob, span_warning("[quirk_coffin] requires blood to operate, which you are currently lacking. Your connection to the other-world fades once again.")) + + // Stop processing and return + STOP_PROCESSING(SSquirks, src) + return + + // Define initial health + var/health_start = quirk_mob.health + + // Define health needing updates + var/need_mob_update = FALSE + + // Queue healing compatible damage types + need_mob_update += quirk_holder.adjustBruteLoss(BLOODFLEDGE_HEAL_AMT, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC) + need_mob_update += quirk_holder.adjustFireLoss(BLOODFLEDGE_HEAL_AMT, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC) + need_mob_update += quirk_holder.adjustToxLoss(BLOODFLEDGE_HEAL_AMT, updating_health = FALSE, required_biotype = MOB_ORGANIC, forced = TRUE) + need_mob_update += quirk_holder.adjustOxyLoss(BLOODFLEDGE_HEAL_AMT, updating_health = FALSE, required_biotype = MOB_ORGANIC) + + // Check if healing will be applied + if(need_mob_update) + // Update health + quirk_holder.updatehealth() + + // No healing will occur + else + // Warn user + to_chat(quirk_mob, span_warning("[quirk_coffin] cannot mend any more damage to your body.")) + + // Stop processing and return + STOP_PROCESSING(SSquirks, src) + return + + // Determine healed amount + var/health_restored = quirk_mob.health - health_start + + // Remove a resource as compensation for healing + // Amount is equal to healing done + + // Check if using nutrition mode + if(use_nutrition) + quirk_mob.adjust_nutrition(health_restored*-1) + + // Using blood volume mode + else + quirk_mob.blood_volume -= (health_restored*-1) + + +/datum/quirk/item_quirk/bloodfledge/remove() + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove quirk ability action datums + var/datum/action/cooldown/bloodfledge/revive/act_revive = locate() in quirk_mob.actions + act_revive?.Remove(quirk_mob) + + // Remove quirk language + quirk_mob.remove_language(/datum/language/vampiric, ALL, LANGUAGE_QUIRK) + + // Unregister examine text + UnregisterSignal(quirk_holder, COMSIG_ATOM_EXAMINE) + + /** + * Hemophage check + * + * Check if the quirk holder is a hemophage. + * Ignore remaining features if they are + */ + if(ishemophage(quirk_mob)) + return + + // Remove quirk traits + REMOVE_TRAIT(quirk_mob, TRAIT_LIVERLESS_METABOLISM, ROUNDSTART_TRAIT) + //REMOVE_TRAIT(quirk_mob, TRAIT_NOTHIRST, ROUNDSTART_TRAIT) + + // Check if species should still be vampiric + if(!(quirk_mob.dna?.species?.inherent_biotypes & MOB_VAMPIRIC)) + // Remove vampiric biotype + quirk_mob.mob_biotypes -= MOB_VAMPIRIC + + // Remove quirk ability action datums + var/datum/action/cooldown/bloodfledge/bite/act_bite = locate() in quirk_mob.actions + act_bite?.Remove(quirk_mob) + + // Remove profane penalties + REMOVE_TRAIT(quirk_holder, TRAIT_CHAPEL_WEAKNESS, TRAIT_BLOODFLEDGE) + REMOVE_TRAIT(quirk_holder, TRAIT_HOLYWATER_WEAKNESS, TRAIT_BLOODFLEDGE) + +/datum/quirk/item_quirk/bloodfledge/add_unique(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Create vampire ID card + var/obj/item/card/id/advanced/quirk/bloodfledge/id_vampire = new(get_turf(quirk_mob)) + + // Define default card type name + var/card_name_type = "Blood" + + // Define possible blood prefix + var/blood_prefix = quirk_mob.get_blood_prefix() + + // Check if species blood prefix was returned + if(blood_prefix) + // Set new card type + card_name_type = blood_prefix + + // Define operative alias + var/operative_alias = client_source?.prefs?.read_preference(/datum/preference/name/operative_alias) + + // Update card information + // Try to use operative name + if(operative_alias) + id_vampire.registered_name = operative_alias + + // Fallback to default name + else + id_vampire.registered_name = quirk_mob.real_name + + // Attempt to set chronological age + if(quirk_mob.chrono_age) + id_vampire.registered_age = quirk_mob.chrono_age + + // Set assignment overrides + id_vampire.assignment = "[card_name_type]fledge" + id_vampire.trim?.assignment = "[card_name_type]fledge" + + // Update label + id_vampire.update_label() + + // Check for bank account + if(!quirk_mob.account_id) + return + + // Define bank account + var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[quirk_mob.account_id]"] + + // Add to cards list + account.bank_cards += src + + // Assign account + id_vampire.registered_account = account + + // Give ID card + give_item_to_holder(id_vampire, + list( + LOCATION_LPOCKET = ITEM_SLOT_LPOCKET, + LOCATION_RPOCKET = ITEM_SLOT_RPOCKET, + LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, + LOCATION_HANDS = ITEM_SLOT_HANDS, + ) + ) + +/** + * Special examine text for Bloodfledges + * * Displays hunger level notices + * * Indicates that the holder has a revive ability +*/ +/datum/quirk/item_quirk/bloodfledge/proc/quirk_examine_bloodfledge(atom/examine_target, mob/living/carbon/human/examiner, list/examine_list) + SIGNAL_HANDLER + + // Check if human examiner exists + if(!istype(examiner)) + return + + // Check if examiner is dumb + if(HAS_TRAIT(examiner, TRAIT_DUMB)) + // Return with no effects + return + + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Define pronouns + var/holder_they = quirk_holder.p_They() + var/holder_their = quirk_holder.p_Their() + var/holder_are = quirk_holder.p_are() + + // Check if dead + if(quirk_holder.stat >= DEAD) + // Add potential revival text + examine_list += span_info("[holder_their] body radiates an unnatural energy, as though [quirk_holder.p_they()] could spring to life at any moment...") + + // Define hunger texts + var/examine_hunger_public + var/examine_hunger_secret + + // Check hunger levels + switch(quirk_mob.nutrition) + // Hungry + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + examine_hunger_secret = "[holder_they] [holder_are] blood starved!" + examine_hunger_public = "[holder_they] seem[quirk_holder.p_s()] on edge from something." + + // Starving + if(0 to NUTRITION_LEVEL_STARVING) + examine_hunger_secret = "[holder_they] [holder_are] in dire need of blood!" + examine_hunger_public = "[holder_they] [holder_are] radiating an aura of frenzied hunger!" + + // Invalid hunger + else + // Return with no message + return + + // Check if examiner shares the quirk + if(isbloodfledge(examiner)) + // Add detection text + examine_list += span_info("[holder_their] hunger makes it easy to identify [quirk_holder.p_them()] as a fellow sanguine!") + + // Add hunger text + examine_list += span_warning(examine_hunger_secret) + + // Check if public hunger text exists + else + // Add hunger text + examine_list += span_warning(examine_hunger_public) + +/** + * Coffin check for Bloodfledges. Enables quirk processing if all conditions pass. + * + * Requires the following + * * Organic mob biotype + * * No HOLY anti-magic + * * No garlic reagent + * * No stake embedded +*/ +/datum/quirk/item_quirk/bloodfledge/proc/on_enter_coffin(mob/living/carbon/target, obj/structure/closet/crate/coffin/coffin, mob/living/carbon/user) + SIGNAL_HANDLER + + // Check for organic user + if(!(user.mob_biotypes & MOB_ORGANIC)) + // Warn user and return + to_chat(quirk_holder, span_warning("Your body don't respond to [coffin]'s sanguine connection! Regeneration will not be possible.")) + return + + // Check for holy anti-magic + if(user.can_block_magic(MAGIC_RESISTANCE_HOLY)) + // Warn user and return + to_chat(quirk_holder, span_warning("[coffin] fails to form a connection with your body amidst the strong magical interference!!")) + return + + // Check for garlic + if(user.has_reagent(/datum/reagent/consumable/garlic, 5)) + // Warn user and return + to_chat(quirk_holder, span_warning("The Allium Sativum in your system interferes with your regeneration!")) + return + + // Check for stake + if(user.am_staked()) + // Warn user and return + to_chat(quirk_holder, span_warning("Your body cannot regenerate while impaled with a stake!")) + return + + // User is allowed to heal! + + // Alert user + to_chat(quirk_holder, span_good("[coffin] begins to mend your body!")) + + // Start processing + START_PROCESSING(SSquirks, src) + +/** + * Staked interaction for Bloodfledges + * * Causes instant death if the target is unconscious + * * Warns normally if the target is conscious +*/ +/datum/quirk/item_quirk/bloodfledge/proc/on_staked(atom/target, forced) + SIGNAL_HANDLER + + // Check if unconscious + if(quirk_holder.IsSleeping() || quirk_holder.stat >= UNCONSCIOUS) + // Warn the user + to_chat(target, span_userdanger("You have been staked while unconscious!")) + + // Kill the user + quirk_holder.death() + + // Log the death + quirk_holder.investigate_log("Died as a bloodfledge from staking.", INVESTIGATE_DEATHS) + + // Do nothing else + return + + // User is conscious + // Warn the user of staking + to_chat(target, span_userdanger("You have been staked! Your powers are useless while it remains in place.")) + target.balloon_alert(target, "you have been staked!") + +/** + * Blood nourishment for Bloodfledges + * * Checks if the blood was synthesized or from an invalid mob + * * Checks if the owner tried to drink their own blood + * * Converts any valid blood into Notriment +*/ +/datum/quirk/item_quirk/bloodfledge/proc/on_consume_blood(mob/living/target, datum/reagent/blood/handled_reagent, amount, data) + SIGNAL_HANDLER + + // Check for data + if(!data) + // Log warning and return + log_game("[quirk_holder] attempted to ingest blood that had no data!") + return + + // Define blood DNA + var/blood_DNA = data["blood_DNA"] + + // Debug output + #ifdef TESTING + to_chat(quirk_holder, span_boldwarning("INGESTED DNA IS: [blood_DNA]")) + #endif + + // Check for valid DNA + if(!blood_DNA) + // Warn user + to_chat(quirk_holder, span_warning("Something about that blood tasted terribly wrong...")) + + // Add mood penalty + quirk_holder.add_mood_event(QMOOD_BFLED_DRANK_BLOOD_FAKE, /datum/mood_event/bloodfledge/drankblood/blood_fake) + + // End here + return + + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Define quirk mob's DNA + var/quirk_mob_dna = quirk_mob?.dna?.unique_enzymes + + // Debug output + #ifdef TESTING + to_chat(quirk_holder, span_boldwarning("YOUR DNA IS: [quirk_mob_dna]")) + #endif + + // Check for own blood + if(blood_DNA == quirk_mob_dna) + // Warn user + to_chat(quirk_holder, span_warning("You should know better than to drink your own blood...")) + + // Add mood penalty + quirk_holder.add_mood_event(QMOOD_BFLED_DRANK_BLOOD_SELF, /datum/mood_event/bloodfledge/drankblood/blood_self) + + // End here + return + + // Check for valid reagent + if(ispath(handled_reagent)) + // Remove reagent + quirk_holder.reagents.remove_reagent(handled_reagent, amount) + + // Add Notriment + quirk_holder.reagents.add_reagent(/datum/reagent/consumable/notriment, amount) + +// +// Bloodfledge actions +// + +// Action: Base +/datum/action/cooldown/bloodfledge + name = "Broken Bloodfledge Ability" + desc = "You shouldn't be seeing this!" + background_icon = 'modular_zubbers/icons/mob/actions/bloodsucker.dmi' + background_icon_state = "vamp_power_off" + button_icon = 'modular_zubbers/icons/mob/actions/bloodsucker.dmi' + button_icon_state = "power_feed" + buttontooltipstyle = "cult" + + /// Toggle between using blood volume or nutrition. Blood volume is used for hemophages. + var/use_nutrition = TRUE + +/datum/action/cooldown/bloodfledge/Grant() + . = ..() + + // Check if user is a hemophage + if(ishemophage(owner)) + // Disable nutrition mode + use_nutrition = FALSE + +/** + * Check if Bloodfledge power is allowed to be used + * + * Requires the following: + * * No HOLY anti-magic + * * No garlic reagent + * * No stake embedded + * * Not just a brain +*/ +/datum/action/cooldown/bloodfledge/proc/can_use(mob/living/carbon/action_owner) + // Check for deleted owner + if(QDELETED(owner)) + return FALSE + + // Check for holiness + if(owner.can_block_magic(MAGIC_RESISTANCE_HOLY)) + // Warn user and return + to_chat(owner, span_warning("A holy force prevents you from using your powers!")) + owner.balloon_alert(owner, "holy interference!") + return FALSE + + // Check for garlic + if(action_owner.has_reagent(/datum/reagent/consumable/garlic, 5)) + // Warn user and return + to_chat(owner, span_warning("The Allium Sativum in your system is stifling your powers!")) + owner.balloon_alert(owner, "garlic interference!") + return FALSE + + // Check for stake + if(action_owner.am_staked()) + to_chat(owner, span_warning("Your powers are useless while you have a stake in your chest!")) + owner.balloon_alert(owner, "staked!") + return FALSE + + // Check if just a brain + if(isbrain(owner)) + to_chat(owner, span_warning("You think extra hard about how you can't do this right now!")) + owner.balloon_alert(owner, "just a brain!") + return FALSE + + // Action can be used + return TRUE + +// Action: Bite +/datum/action/cooldown/bloodfledge/bite + name = "Fledgling Bite" + desc = "Sink your fangs into the person you are grabbing, and attempt to drink their blood." + button_icon_state = "power_feed" + cooldown_time = BLOODFLEDGE_COOLDOWN_BITE + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED | AB_CHECK_LYING | AB_CHECK_PHASED + + /// How long it takes to bite a target + var/time_interact = BLOODFLEDGE_DRAIN_TIME + + /// Reagent holder, used to change reaction type + var/datum/reagents/blood_bank + +// Corrupted tongue variant +/datum/action/cooldown/bloodfledge/bite/corrupted_tongue + name = "Sanguine Bite" + +/datum/action/cooldown/bloodfledge/bite/Grant() + . = ..() + + // Check if using nutrition mode + if(use_nutrition) + // Create reagent holder + blood_bank = new(BLOODFLEDGE_BANK_CAPACITY) + + // Check for voracious + if(HAS_TRAIT(owner, TRAIT_VORACIOUS)) + // Make times twice as fast + cooldown_time *= 0.5 + time_interact*= 0.5 + +/datum/action/cooldown/bloodfledge/bite/Activate() + // Check if powers are allowed + if(!can_use(owner)) + return FALSE + + // Define action owner carbon mob + var/mob/living/carbon/action_owner = owner + + // Check for any grabbed target + if(!action_owner.pulling) + // Warn the user, then return + //to_chat(action_owner, span_warning("You need a victim first!")) + action_owner.balloon_alert(action_owner, "need a victim!") + return FALSE + + // Check for muzzle + // Unimplemented here + /* + if(action_owner.is_muzzled()) + // Warn the user, then return + to_chat(action_owner, span_warning("You can't bite things while muzzled!")) + owner.balloon_alert(owner, "muzzled!") + return FALSE + */ + + // Check for covered mouth + if(action_owner.is_mouth_covered()) + // Warn the user, then return + to_chat(action_owner, span_warning("You can't bite things with your mouth covered!")) + owner.balloon_alert(owner, "mouth covered!") + return FALSE + + // Using nutrition mode + if(use_nutrition) + // Limit maximum nutrition + if(action_owner.nutrition >= NUTRITION_LEVEL_FAT) + // Warn the user, then return + to_chat(action_owner, span_warning("You are too full to drain any more.")) + owner.balloon_alert(owner, "too full!") + return + + // Limit maximum potential nutrition + if(action_owner.nutrition + BLOODFLEDGE_DRAIN_AMT >= NUTRITION_LEVEL_FAT) + // Warn the user, then return + to_chat(action_owner, span_warning("You would become too full by draining any more blood.")) + owner.balloon_alert(owner, "too full!") + return + + // Using blood volume mode + else + // Limit maximum blood volume + if(action_owner.blood_volume >= BLOOD_VOLUME_MAXIMUM) + // Warn the user, then return + to_chat(action_owner, span_warning("Your body contains too much blood to drain any more.")) + owner.balloon_alert(owner, "too full!") + return + + // Limit maximum potential blood volume + if(action_owner.blood_volume + BLOODFLEDGE_DRAIN_AMT >= BLOOD_VOLUME_MAXIMUM) + // Warn the user, then return + to_chat(action_owner, span_warning("You body would become overwhelmed by draining any more blood.")) + owner.balloon_alert(owner, "too full!") + return + + // Define pulled target + var/pull_target = action_owner.pulling + + // Define bite target + var/mob/living/carbon/human/bite_target + + /// Does action owner dumb has the dumb trait? Changes the result of some failure interactions. + var/action_owner_dumb = HAS_TRAIT(action_owner, TRAIT_DUMB) + + // Face the target + action_owner.face_atom(pull_target) + + // Check if the target is carbon + if(iscarbon(pull_target)) + // Set the bite target + bite_target = pull_target + + // Or cocooned carbon + else if(istype(pull_target,/obj/structure/spider/cocoon)) + // Define if cocoon has a valid target + // This cannot use pull_target + var/possible_cocoon_target = locate(/mob/living/carbon/human) in action_owner.pulling.contents + + // Check defined cocoon target + if(possible_cocoon_target) + // Set the bite target + bite_target = possible_cocoon_target + + // Or a blood tomato + else if(istype(pull_target,/obj/item/food/grown/tomato/blood)) + // Set message based on dumbness + var/message_tomato_suffix = (action_owner_dumb ? ", and absorb it\'s delicious vegan-friendly blood!" : "! It's not very nutritious.") + // Warn the user, then return + to_chat(action_owner, span_danger("You plunge your fangs into [pull_target][message_tomato_suffix]")) + return + + // This doesn't actually interact with the item + + // Or none of the above + else + // Set message based on dumbness + var/message_invalid_target = (action_owner_dumb ? "You bite at [pull_target], but nothing seems to happen" : "You can't drain blood from [pull_target]!") + // Warn the user, then return + to_chat(action_owner, span_warning(message_invalid_target)) + return + + // Define selected zone + var/target_zone = action_owner.zone_selected + + // Check if target can be penetrated + // Bypass pierce immunity so feedback can be provided later + if(!bite_target.can_inject(action_owner, target_zone)) + // Warn the user, then return + to_chat(action_owner, span_warning("There\'s no exposed flesh or thin material in that region of [bite_target]'s body. You're unable to bite them!")) + return + + // Check targeted body part + var/obj/item/bodypart/bite_bodypart = bite_target.get_bodypart(target_zone) + + // Define zone name + var/target_zone_name = "flesh" + + /// Does the target zone have unique interactions? + var/target_zone_effects = FALSE + + /** + * If targeted zone should be checked + * * Uses dismember check to determine if it can be missing. + * * Missing limbs are assumed to be dismembered. + */ + var/target_zone_check = bite_bodypart?.can_dismember() || TRUE + + // Set zone name based on region + // Also checks for some protections + switch(target_zone) + if(BODY_ZONE_HEAD) + target_zone_name = "neck" + + if(BODY_ZONE_CHEST) + target_zone_name = "shoulder" + + if(BODY_ZONE_L_ARM) + target_zone_name = "left arm" + + if(BODY_ZONE_R_ARM) + target_zone_name = "right arm" + + if(BODY_ZONE_L_LEG) + target_zone_name = "left thigh" + + if(BODY_ZONE_R_LEG) + target_zone_name = "right thigh" + + if(BODY_ZONE_PRECISE_EYES) + // Check if eyes exist and are exposed + if(!bite_target.has_eyes() == REQUIRE_GENITAL_EXPOSED) + // Warn user and return + to_chat(action_owner, span_warning("You can't find [bite_target]'s eyes to bite them!")) + owner.balloon_alert(owner, "no eyes?") + return + + // Set region data normally + target_zone_name = "eyes" + target_zone_check = FALSE + target_zone_effects = TRUE + + if(BODY_ZONE_PRECISE_MOUTH) + // Check if mouth is covered + if(bite_target.is_mouth_covered()) + to_chat(action_owner, span_warning("You can't reach [bite_target]'s lips to bite them!")) + owner.balloon_alert(owner, "no lips?") + return + + // Set region data normally + target_zone_name = "lips" + target_zone_check = FALSE + target_zone_effects = TRUE + + if(BODY_ZONE_PRECISE_GROIN) + target_zone_name = "groin" + target_zone_check = FALSE + + if(BODY_ZONE_PRECISE_L_HAND) + target_zone_name = "left wrist" + + if(BODY_ZONE_PRECISE_R_HAND) + target_zone_name = "right wrist" + + if(BODY_ZONE_PRECISE_L_FOOT) + target_zone_name = "left ankle" + + if(BODY_ZONE_PRECISE_R_FOOT) + target_zone_name = "right ankle" + + // Check if target should be checked + if(target_zone_check) + // Check if bodypart exists + if(!bite_bodypart) + // Warn user and return + to_chat(action_owner, span_warning("[bite_target] doesn't have a [target_zone_name] for you to bite!")) + owner.balloon_alert(owner, "no [target_zone_name]?") + return + + // Check if bodypart is organic + if(!IS_ORGANIC_LIMB(bite_bodypart)) + // Display local message + action_owner.visible_message(span_danger("[action_owner] tries to bite [bite_target]'s [target_zone_name], but is unable to penetrate the mechanical prosthetic!"), span_warning("You attempt to bite [bite_target]'s [target_zone_name], but can't penetrate the mechanical prosthetic!"), ignored_mobs=bite_target) + + // Warn user + to_chat(bite_target, span_warning("[action_owner] tries to bite your [target_zone_name], but is unable to penetrate the mechanical prosthetic!")) + + // Play metal hit sound + playsound(bite_target, "sound/effects/clang.ogg", 30, 1, -2) + + // Start cooldown early to prevent spam + StartCooldown() + + // Return without further effects + return + + // Check for anti-magic + if(bite_target.can_block_magic(MAGIC_RESISTANCE_HOLY)) + // Check for a dumb user + if(action_owner_dumb) + // Display local message + action_owner.visible_message(span_danger("[action_owner] tries to bite [bite_target]'s [target_zone_name], but bursts into flames just as [action_owner.p_they()] come[action_owner.p_s()] into contact with [bite_target.p_them()]!"), span_userdanger("Surges of pain course through your body as you attempt to bite [bite_target]! What were you thinking?"), ignored_mobs=bite_target) + + // Warn target + to_chat(bite_target, span_warning("[action_owner] tries to bite you, but bursts into flames just as [action_owner.p_they()] come[action_owner.p_s()] into contact with you!")) + + // Stop grabbing + action_owner.stop_pulling() + + // Ignite action owner + action_owner.adjust_fire_stacks(2) + action_owner.ignite_mob() + + // Return with no further effects + return + + // Warn the user and target, then return + to_chat(bite_target, span_warning("[action_owner] tries to bite your [target_zone_name], but stops before touching you!")) + to_chat(action_owner, span_warning("[bite_target] is blessed! You stop just in time to avoid catching fire.")) + return + + // Check for garlic in the bloodstream + if(bite_target.has_reagent(/datum/reagent/consumable/garlic, 5)) + // Check for a dumb user + if(action_owner_dumb) + // Display local message + action_owner.visible_message(span_danger("[action_owner] tries to bite [bite_target]'s [target_zone_name], but immediately recoils in disgust upon touching [bite_target.p_them()]!"), span_userdanger("An intense wave of disgust washes over your body as you attempt to bite [bite_target]! What were you thinking?"), ignored_mobs=bite_target) + + // Warn target + to_chat(bite_target, span_warning("[action_owner] tries to bite your [target_zone_name], but recoils in disgust just as [action_owner.p_they()] come[action_owner.p_s()] into contact with you!")) + + // Stop grabbing + action_owner.stop_pulling() + + // Add disgust + action_owner.adjust_disgust(10) + + // Vomit + action_owner.vomit() + + // Return with no further effects + return + + // Warn the user and target, then return + to_chat(bite_target, span_warning("[action_owner] leans in to bite your [target_zone_name], but is warded off by your Allium Sativum!")) + to_chat(action_owner, span_warning("You sense that [bite_target] is protected by Allium Sativum, and refrain from biting [bite_target.p_them()].")) + return + + // Define bite target's blood volume + var/target_blood_volume = bite_target.blood_volume + + // Check for sufficient blood volume + if(target_blood_volume < BLOODFLEDGE_DRAIN_AMT) + // Warn the user, then return + to_chat(action_owner, span_warning("There's not enough blood in [bite_target]!")) + return + + // Check if total blood would become too low + if((target_blood_volume - BLOODFLEDGE_DRAIN_AMT) <= BLOOD_VOLUME_OKAY) + // Check for a dumb user + if(action_owner_dumb) + // Warn the user, but allow + to_chat(action_owner, span_warning("You pay no attention to [bite_target]'s blood volume, and bite [bite_target.p_their()] [target_zone_name] without hesitation.")) + + // Check for aggressive grab + else if(action_owner.grab_state < GRAB_AGGRESSIVE) + // Warn the user, then return + to_chat(action_owner, span_warning("You sense that [bite_target] is running low on blood. You'll need a tighter grip on [bite_target.p_them()] to continue.")) + return + + // Check for pacifist + else if(HAS_TRAIT(action_owner, TRAIT_PACIFISM)) + // Warn the user, then return + to_chat(action_owner, span_warning("You can't drain any more blood from [bite_target] without hurting [bite_target.p_them()]!")) + return + + // Check for pierce immunity + if(HAS_TRAIT(bite_target, TRAIT_PIERCEIMMUNE)) + // Display local chat message + action_owner.visible_message(span_danger("[action_owner] tries to bite down on [bite_target]'s [target_zone_name], but can't seem to pierce [bite_target.p_them()]!"), span_danger("You try to bite down on [bite_target]'s [target_zone_name], but are completely unable to pierce [bite_target.p_them()]!"), ignored_mobs=bite_target) + + // Warn bite target + to_chat(bite_target, span_userdanger("[action_owner] tries to bite your [target_zone_name], but is unable to piece you!")) + + // Return without further effects + return + + // Check for target zone special effects + if(target_zone_effects) + // Check if biting eyes or mouth + if((target_zone == BODY_ZONE_PRECISE_EYES) || (target_zone == BODY_ZONE_PRECISE_MOUTH)) + // Check if biting target with proto-type face + // Snout type is a string that cannot use subtype search + if(findtext(bite_target.dna?.features["snout"], "Synthetic Lizard")) + // Display local chat message + action_owner.visible_message(span_notice("[action_owner]'s fangs clank harmlessly against [bite_target]'s face-screen!"), span_notice("Your fangs clank harmlessly against [bite_target]'s face-screen!"), ignored_mobs=bite_target) + + // Alert bite target + to_chat(bite_target, span_notice("[action_owner]'s fangs clank harmlessly against your face-screen")) + + // Play glass tap sound + playsound(bite_target, 'sound/effects/glass/glasshit.ogg', 50, 1, -2) + + // Start cooldown early to prevent spam + StartCooldown() + + // Return without further effects + return + + // Check for strange bite regions + switch(target_zone) + // Zone is eyes + if(BODY_ZONE_PRECISE_EYES) + // Define target's eyes + var/obj/item/organ/internal/eyes/target_eyes = bite_target.get_organ_slot(ORGAN_SLOT_EYES) + + // Check if eyes exist + // This should always be the case since eyes exposed was checked above + if(!target_eyes) + // Warn user and return + to_chat(bite_target, span_userdanger("Something has gone terribly wrong with [bite_target]'s eyes! Please report this to a coder!")) + return + + // Check for cybernetic eyes + if(IS_ROBOTIC_ORGAN(target_eyes)) + // Warn users and return + to_chat(action_owner, span_danger("Your fangs aren't powerful enough to penetrate robotic eyes!")) + to_chat(bite_target, span_danger("[action_owner] tries to bite into your [target_eyes], but can't break through!")) + return + + // Display warning + to_chat(bite_target, span_userdanger("Your [target_eyes] rupture in pain as [action_owner]'s fangs pierce their surface!")) + + // Blur vision equal to drunkenness + bite_target.adjust_eye_blur_up_to(4 SECONDS, 20 SECONDS) + + // Add organ damage + target_eyes.apply_organ_damage(rand(10, 20)) + + // Zone is mouth + if(BODY_ZONE_PRECISE_MOUTH) + // Cause temporary stuttering + bite_target.set_stutter_if_lower(10 SECONDS) + + // Display local chat message + action_owner.visible_message(span_danger("[action_owner] bites down on [bite_target]'s [target_zone_name]!"), span_danger("You bite down on [bite_target]'s [target_zone_name]!"), ignored_mobs=bite_target) + + // Play a bite sound effect + playsound(action_owner, 'sound/items/weapons/bite.ogg', 30, 1, -2) + + // Warn bite target + to_chat(bite_target, span_userdanger("[action_owner] has bitten your [target_zone_name], and is trying to drain your blood!")) + + // Try to perform action timer + if(!do_after(action_owner, time_interact, target = bite_target)) + // When failing + // Display a local chat message + action_owner.visible_message(span_danger("[action_owner]'s fangs are prematurely torn from [bite_target]'s [target_zone_name], spilling some of [bite_target.p_their()] blood!"), span_danger("Your fangs are prematurely torn from [bite_target]'s [target_zone_name], spilling some of [bite_target.p_their()] blood!"), ignored_mobs=bite_target) + + // Warn bite target + to_chat(bite_target, span_userdanger("[action_owner]\'s fangs are prematurely torn from your [target_zone_name], spilling some of your blood!")) + + // Bite target "drops" 20% of the blood + // This creates large blood splatter + bite_target.bleed((BLOODFLEDGE_DRAIN_AMT*0.2), FALSE) + + // Play splatter sound + playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1) + + // Check for masochism + if(!HAS_TRAIT(bite_target, TRAIT_MASOCHISM)) + // Force bite_target to play the scream emote + bite_target.emote("scream") + + // Log the biting action failure + log_combat(action_owner,bite_target,"bloodfledge bitten (interrupted)") + + // Add target's blood to quirk holder and themselves + bite_target.add_mob_blood(bite_target) + action_owner.add_mob_blood(bite_target) + + // Check if body part is valid for bleeding + // This reuses the dismember-able check + if(target_zone_check) + // Cause minor bleeding + bite_bodypart.adjustBleedStacks(2) + + // Apply minor damage + bite_bodypart.receive_damage(brute = rand(4,8), sharpness = SHARP_POINTY) + + // Start cooldown early + // This is to prevent bite interrupt spam + StartCooldown() + + // Return + return + else + /// Is this valid nourishing blood? Does not grant nutrition if FALSE. + var/blood_valid = TRUE + + /// Should blood be transferred anyway? Used when blood_valid is FALSE. + var/blood_transfer = FALSE + + /// Name of exotic blood substitute determined by species + var/blood_name = "blood" + + // Check if target has exotic bloodtype + if(bite_target.dna?.species?.exotic_bloodtype) + // Define blood types for owner and target + var/blood_type_owner = action_owner.dna?.species?.exotic_bloodtype + var/blood_type_target = bite_target.dna?.species?.exotic_bloodtype + + /// Define if owner and target blood types match. Used for providing a mood bonus and immunity to exotic blood mood penalties. + var/blood_type_match = (blood_type_owner == blood_type_target ? TRUE : FALSE) + + // Check if types matched + if(blood_type_match) + // Add positive mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_MATCH, /datum/mood_event/bloodfledge/drankblood/exotic_matched) + + // Switch for target's blood type + switch(blood_type_target) + // Lizard blood + if("L") + // Set blood type name + blood_name = "reptilian blood" + + // No penalty + + // Bug blood + // Not used here + /* + if("BUG") + // Set blood type name + blood_name = "hemolymph" + + // Check if blood types match + if(!blood_type_match) + // Mark blood as invalid + blood_valid = FALSE + + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_BUG, /datum/mood_event/bloodfledge/drankblood/insect) + */ + + // Vampire blood + if("U") + // Set blood type name + blood_name = "sanguine blood" + + // EDIT: Allowed again + /* + // Don't drink from vampires! + // Mark blood as invalid + blood_valid = FALSE + + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_VAMP, /datum/mood_event/bloodfledge/drankblood/vampire) + */ + + // Ethreal blood + if("LE") + // Set blood type name + blood_name = "liquid electricity" + + // Mark blood as invalid + blood_valid = FALSE + + // Allow transferring blood from this + blood_transfer = TRUE + + // Cause neutral mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_ETHER, /datum/mood_event/bloodfledge/drankblood/ethereal) + + // Edge case + else + // Set blood type name + blood_name = "unknown exotic bloodtype" + + // Mark blood as invalid + blood_valid = FALSE + + // Check if target has exotic blood reagent + // Second check for the separate exotic_blood variable + else if(bite_target.dna?.species?.exotic_blood) + // Define blood types for owner and target + var/blood_type_owner = action_owner.dna?.species?.exotic_blood + var/blood_type_target = bite_target.dna?.species?.exotic_blood + + /// Define if owner and target blood types match. Used for providing a mood bonus and immunity to exotic blood mood penalties. + var/blood_type_match = (blood_type_owner == blood_type_target ? TRUE : FALSE) + + // Check if types matched + if(blood_type_match) + // Add positive mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_MATCH, /datum/mood_event/bloodfledge/drankblood/exotic_matched) + + // Check for target's blood type + switch(blood_type_target) + // Synthetic blood + if(/datum/reagent/fuel/oil) + // Mark blood as invalid + blood_valid = FALSE + + // Set blood type name + blood_name = "oil" + + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_SYNTH, /datum/mood_event/bloodfledge/drankblood/synth) + + // Slime blood + if(/datum/reagent/toxin/slimejelly) + // Mark blood as invalid + blood_valid = FALSE + + // Allow transferring blood from this + blood_transfer = TRUE + + // Set blood type name + blood_name = "slime jelly" + + // Check if blood types match + if(!blood_type_match) + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_SLIME, /datum/mood_event/bloodfledge/drankblood/slime) + + // Podperson blood + if(/datum/reagent/water) + // Set blood type name + blood_name = "water" + + // Mark blood as invalid + blood_valid = FALSE + + // Cause neutral mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_POD, /datum/mood_event/bloodfledge/drankblood/podperson) + + // Snail blood + if(/datum/reagent/lube) + // Set blood type name + blood_name = "space lube" + + // Mark blood as invalid + blood_valid = FALSE + + // Check if blood types match + if(!blood_type_match) + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_SNAIL, /datum/mood_event/bloodfledge/drankblood/snail) + + // Skrell blood + if(/datum/reagent/copper) + // Set blood type name + blood_name = "copper" + + // Mark blood as invalid + blood_valid = FALSE + + // Check if blood types match + if(!blood_type_match) + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_SKREL, /datum/mood_event/bloodfledge/drankblood/skrell) + + // Xenomorph Hybrid blood + if(/datum/reagent/toxin/acid) + // Set blood type name + blood_name = "sulfuric acid" + + // Mark blood as invalid + blood_valid = FALSE + + // Allow transferring blood from this + blood_transfer = TRUE + + // Check if blood types match + if(!blood_type_match) + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_XENO, /datum/mood_event/bloodfledge/drankblood/xeno) + + // Edge case + else + // Set blood type name + blood_name = "unknown exotic blood" + + // Mark blood as invalid + blood_valid = FALSE + + // Check if bite target has any blood + // Checked later since some species have NOBLOOD and exotic blood type + else if(HAS_TRAIT(bite_target, TRAIT_NOBLOOD)) + // Warn the user and target + to_chat(bite_target, span_warning("[action_owner] bit your [target_zone_name] in an attempt to drain your blood, but couldn't find any!")) + to_chat(action_owner, span_warning("[bite_target] doesn't have any blood to drink!")) + + // Start cooldown early to prevent sound spam + StartCooldown() + + // Return without effects + return + + // End of exotic blood checks + + // Define user's remaining capacity to absorb blood + var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - action_owner.blood_volume + var/drained_blood = min(target_blood_volume, BLOODFLEDGE_DRAIN_AMT, blood_volume_difference) + + // Transfer reagents from target to action owner + // Limited to a maximum 10% of bite amount (default 10u) + bite_target.reagents.trans_to(action_owner, (drained_blood*0.1)) + + // Alert the bite target and local user of success + // Yes, this is AFTER the message for non-valid blood + to_chat(bite_target, span_danger("[action_owner] has taken some of your [blood_name]!")) + to_chat(action_owner, span_notice("You've drained some of [bite_target]'s [blood_name]!")) + + // Check if action owner received valid blood + if(blood_valid) + // Using nutrition mode + if(use_nutrition) + // Add blood reagent to reagent holder + blood_bank.add_reagent(/datum/reagent/blood/, drained_blood, bite_target.get_blood_data()) + + // Transfer reagent to action owner + blood_bank.trans_to(action_owner, drained_blood, methods = INGEST) + + // Remove all reagents + blood_bank.remove_all() + + // Using blood volume mode + else + // Transfer blood directly + bite_target.transfer_blood_to(action_owner, drained_blood, TRUE) + + // Check if blood transfer should occur + else if(blood_transfer) + // Check if action holder's blood volume limit was exceeded + if(action_owner.blood_volume >= BLOOD_VOLUME_MAXIMUM) + // Warn user + to_chat(action_owner, span_warning("You body cannot integrate any more [blood_name]. The remainder will be lost.")) + + // Blood volume limit was not exceeded + else + // Alert user + to_chat(action_owner, span_notice("You body integrates the [blood_name] directly, instead of processing it into nutrition.")) + + // Transfer blood directly + bite_target.transfer_blood_to(action_owner, drained_blood, TRUE) + + // Set drain amount to none + // This prevents double removal + drained_blood = 0 + + // Valid blood was not received + // No direct blood transfer occurred + else + // Warn user of failure + to_chat(action_owner, span_warning("Your body cannot process the [blood_name]!")) + + // Remove blood from bite target + bite_target.blood_volume = clamp(target_blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) + + // Play a heartbeat sound effect + // This was changed to match bloodsucker + playsound(action_owner, 'sound/effects/singlebeat.ogg', 30, 1, -2) + + // Log the biting action success + log_combat(action_owner,bite_target,"bloodfledge bitten (successfully), transferring [blood_name]") + + // Mood events + // Check if bite target is dead or undead + if((bite_target.stat >= DEAD) || (bite_target.mob_biotypes & MOB_UNDEAD)) + // Warn the user + to_chat(action_owner, span_warning("The rotten [blood_name] tasted foul.")) + + // Add disgust + action_owner.adjust_disgust(10) + + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_DEAD, /datum/mood_event/bloodfledge/drankblood/dead) + + // Check if bite target's blood has been depleted + if(!bite_target.blood_volume) + // Warn the user + to_chat(action_owner, span_warning("You've depleted [bite_target]'s [blood_name] supply!")) + + // Cause negative mood + action_owner.add_mood_event(QMOOD_BFLED_DRANK_KILL, /datum/mood_event/bloodfledge/drankblood/killed) + + // Check if bite target has cursed blood + if(HAS_TRAIT(bite_target, TRAIT_CURSED_BLOOD)) + /// Does action owner have the cursed blood quirk? + var/owner_cursed = HAS_TRAIT(action_owner, TRAIT_CURSED_BLOOD) + + // Set chat message based on action owner's trait status + var/warn_message = (owner_cursed ? "You taste the unholy touch of a familiar curse in [bite_target]\'s blood." : "You experience a sensation of intense dread just after drinking from [bite_target]. Something about their blood feels... wrong.") + + // Alert user in chat + to_chat(action_owner, span_notice(warn_message)) + + // Set mood type based on curse status + var/mood_type = (owner_cursed ? /datum/mood_event/bloodfledge/drankblood/cursed_good : /datum/mood_event/bloodfledge/drankblood/cursed_bad) + + // Cause mood event + action_owner.add_mood_event(QMOOD_BFLED_DRANK_CURSE, mood_type) + + // Start cooldown + StartCooldown() + +// Action: Revive +/datum/action/cooldown/bloodfledge/revive + name = "Fledgling Revive" + desc = "Expend all of your remaining energy to escape death." + button_icon_state = "power_strength" + cooldown_time = BLOODFLEDGE_COOLDOWN_REVIVE + +/datum/action/cooldown/bloodfledge/revive/Activate() + // Check if powers are allowed + if(!can_use(owner)) + return FALSE + + // Early check for being dead + // Users are most likely to click this while alive + if(owner.stat != DEAD) + // Warn user and return + //to_chat(action_owner, "You can't use this ability while alive!") + owner.balloon_alert(owner, "not dead!") + return + + /// Define failure messages. Will not revive if any failure message is set. + var/revive_failed + + // Disabled check + /* + // Condition: Mob isn't in a closed coffin + if(!istype(owner.loc, /obj/structure/closet/crate/coffin)) + revive_failed += "\n- You need to be in a closed coffin!" + */ + + // Define mob + var/mob/living/carbon/human/action_owner = owner + + // Condition: Insufficient nutrition + if(use_nutrition) + if(action_owner.nutrition <= NUTRITION_LEVEL_STARVING) + revive_failed += "\n- You're too blood-starved!" + + // Condition: Insufficient blood volume + else + if(action_owner.blood_volume > BLOOD_VOLUME_SURVIVE) + revive_failed += "\n- You don't have enough blood volume left!" + + // Condition: Can be revived + // This is used by revive(), and must be checked here to prevent false feedback + if(!action_owner.can_be_revived()) + revive_failed += "\n- Your body is too weak to sustain life!" + + // Condition: Damage limit, brute + if(action_owner.getBruteLoss() >= MAX_REVIVE_BRUTE_DAMAGE) + revive_failed += "\n- Your body is too battered!" + + // Condition: Damage limit, burn + if(action_owner.getFireLoss() >= MAX_REVIVE_FIRE_DAMAGE) + revive_failed += "\n- Your body is too badly burned!" + + // Condition: Suicide + if(HAS_TRAIT(action_owner, TRAIT_SUICIDED)) + revive_failed += "\n- You chose this path." + + // Condition: Do Not Revive quirk + if(HAS_TRAIT(action_owner, TRAIT_DNR)) + revive_failed += "\n- You only had one chance." + + // Unimplemented here + /* + // Condition: No revivals + if(HAS_TRAIT(action_owner, TRAIT_NOCLONE)) + revive_failed += "\n- You only had one chance." + + // Condition: Demonic contract + if(action_owner.hellbound) + revive_failed += "\n- The soul pact must be honored." + */ + + // Check for failure + if(revive_failed) + // Set combined message + revive_failed = span_warning("You can't revive right now because: [revive_failed]") + + // Alert user in chat of failure + to_chat(action_owner, revive_failed) + + // Return + return + + // Remove oxygen damage + action_owner.adjustOxyLoss(-100, FALSE) + + // Heal and revive the action owner + action_owner.heal_and_revive() + + // Check if health is too low to use revive() + // Obsolete as of heal_and_revive + /* + if(action_owner.health <= HEALTH_THRESHOLD_DEAD) + // Set health high enough to revive + // Based on defib.dm + + // Define damage values + var/damage_brute = action_owner.getBruteLoss() + var/damage_burn = action_owner.getFireLoss() + var/damage_tox = action_owner.getToxLoss() + var/damage_oxy = action_owner.getOxyLoss() + var/damage_brain = action_owner.get_organ_loss(ORGAN_SLOT_BRAIN) + + // Define total damage + var/damage_total = damage_brute + damage_burn + damage_tox + damage_oxy + damage_brain + + // Define to prevent redundant math + // Equal to HALFWAYCRITDEATH in defib.dm + var/health_half_crit = action_owner.health - ((HEALTH_THRESHOLD_CRIT + HEALTH_THRESHOLD_DEAD) * 0.5) + + // Adjust damage types + action_owner.adjustOxyLoss(health_half_crit * (damage_oxy / damage_total), updating_health = FALSE) + action_owner.adjustToxLoss(health_half_crit * (damage_tox / damage_total), updating_health = FALSE) + action_owner.adjustFireLoss(health_half_crit * (damage_burn / damage_total), updating_health = FALSE) + action_owner.adjustBruteLoss(health_half_crit * (damage_brute / damage_total), updating_health = FALSE) + action_owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, health_half_crit * (damage_brain / damage_total)) + + // Update health + action_owner.updatehealth() + + // Check if revival is possible + // This is used by revive(), and must be checked here to prevent false feedback + if(!action_owner.can_be_revived()) + // Warn user + to_chat(action_owner, span_warning("Despite your body's best attempts at mending, it remains too weak to revive! Something this terrible shouldn't be possible!")) + + // Start cooldown anyway, since healing was performed + StartCooldown() + + // Return without revival + return + */ + + // Revive the action owner + //action_owner.revive() + + // Alert the user in chat of success + action_owner.visible_message(span_notice("An ominous energy radiates from the [action_owner.loc]..."), span_warning("You've expended all remaining blood to bring your body back to life!")) + + // Warn user about revive policy + to_chat(action_owner, span_userdanger("[CONFIG_GET(string/blackoutpolicy)]")) + + // Log the revival + action_owner.log_message("revived using a bloodfledge quirk ability.", LOG_GAME) + + // Play a haunted sound effect + playsound(action_owner, 'sound/effects/hallucinations/growl1.ogg', 30, 1, -2) + + // Nutrition mode + if(use_nutrition) + // Set nutrition to starving + action_owner.set_nutrition(NUTRITION_LEVEL_STARVING) + + // Blood volume mode + else + // Set dangerously low blood + action_owner.blood_volume = min(action_owner.blood_volume, BLOOD_VOLUME_SURVIVE) + + // Apply dizzy effect + action_owner.adjust_dizzy_up_to(20 SECONDS, 60 SECONDS) + + // Start cooldown + StartCooldown() + +// +// Bloodfledge mood events +// + +// Base event for drinking blood +/datum/mood_event/bloodfledge/drankblood + mood_change = -4 + timeout = 5 MINUTES + +// Matching exotic blood +/datum/mood_event/bloodfledge/drankblood/exotic_matched + description = "I tasted familiarity from the blood I drank." + mood_change = 2 + +// Insect blood - Currently unused +/datum/mood_event/bloodfledge/drankblood/insect + description = "I drank an insect's hemolymph." + +// Vampire and Hemophage blood +/datum/mood_event/bloodfledge/drankblood/vampire + description = "I drank the forbidden blood of a true sanguine." + +// Ethreal blood +/datum/mood_event/bloodfledge/drankblood/ethereal + description = "I drank the liquid electricity of an ethereal." + +// Synthetic blood +/datum/mood_event/bloodfledge/drankblood/synth + description = "I tried to drink oil from a synth..." + +// Slime blood +/datum/mood_event/bloodfledge/drankblood/slime + description = "I drank the toxic jelly of a slime." + mood_change = -6 + +// Podperson blood +/datum/mood_event/bloodfledge/drankblood/podperson + description = "I drank... water?" + mood_change = 0 + +// Snail blood +/datum/mood_event/bloodfledge/drankblood/snail + description = "I tried to drink space lube..." + +// Skrell blood +/datum/mood_event/bloodfledge/drankblood/skrell + description = "I tried to drink liquid copper." + +// Xenomorph Hybrid blood +/datum/mood_event/bloodfledge/drankblood/xeno + description = "I drank sulfuric acid from a xeno." + mood_change = -6 + +// Dead creature +/datum/mood_event/bloodfledge/drankblood/dead + description = "I drank dead blood. I am better than this." + mood_change = -8 + timeout = 10 MINUTES + +// Killed from feeding +/datum/mood_event/bloodfledge/drankblood/killed + description = "I drank from my victim until they died. I feel...lesser." + mood_change = -12 + timeout = 25 MINUTES + +// Cursed blood matched +/datum/mood_event/bloodfledge/drankblood/cursed_good + description = "I've tasted sympathy from a fellow curse bearer." + mood_change = 1 + +// Cursed blood non-matched +/datum/mood_event/bloodfledge/drankblood/cursed_bad + description = "I can feel a pale curse from the blood I drank." + mood_change = -1 + +// Drinking own blood +/datum/mood_event/bloodfledge/drankblood/blood_self + description = "I drink my own blood. Why would I do that?" + +// Drinking fake blood (no DNA) +/datum/mood_event/bloodfledge/drankblood/blood_fake + description = "I drink artifical blood. I should know better." + +#undef BLOODFLEDGE_DRAIN_AMT +#undef BLOODFLEDGE_DRAIN_TIME +#undef BLOODFLEDGE_COOLDOWN_BITE +#undef BLOODFLEDGE_COOLDOWN_REVIVE +#undef BLOODFLEDGE_BANK_CAPACITY +#undef BLOODFLEDGE_HEAL_AMT diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/breathless.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/breathless.dm new file mode 100644 index 0000000000000..0d5fdf85f087d --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/breathless.dm @@ -0,0 +1,11 @@ +/datum/quirk/breathless + name = "Breathless" + desc = "You no longer require air to function. This also means that administering life-saving maneuvers such as CPR are impossible." + value = 6 + gain_text = span_notice("You no longer need to breathe.") + lose_text = span_notice("You need to breathe again...") + medical_record_text = "Patient demonstrates no requirement for oxygen intake." + mob_trait = TRAIT_NOBREATH + hardcore_value = -4 + icon = FA_ICON_BAN_SMOKING + hidden_quirk = TRUE diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/cloth_eater.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/cloth_eater.dm new file mode 100644 index 0000000000000..4d2b7fc50cbcb --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/cloth_eater.dm @@ -0,0 +1,10 @@ +/datum/quirk/cloth_eater + name = "Clothes Eater" + desc = "You can eat most apparel to gain a boost in mood, and to gain some nutrients. Insects already have this." + value = 2 + gain_text = span_notice("Jumpsuits start to look like an appealing snack.") + lose_text = span_notice("Cloth doesn't seem appetizing anymore.") + medical_record_text = "Patient demonstrates a moth-like tendency to consume clothing items." + mob_trait = TRAIT_CLOTH_EATER + icon = FA_ICON_SHIRT + var/mood_category ="cloth_eaten" diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/flutter.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/flutter.dm new file mode 100644 index 0000000000000..7685621e8ac1f --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/flutter.dm @@ -0,0 +1,32 @@ +/// Amount of drift force to apply when flying +#define FLUTTER_FUNCTIONAL_FORCE 1 NEWTONS +/// Minimum air pressure to allow movement +#define FLUTTER_MIN_PRESSURE WARNING_LOW_PRESSURE + +/datum/quirk/flutter + name = "Flutter" + desc = "You are able to propel yourself forward in pressurized low-gravity environments. Slowing back down may be tricky." + value = 2 + gain_text = span_notice("Your body is prepared to maneuver pressurized low-gravity environments.") + lose_text = span_notice("You forget how to move around in low-gravity.") + medical_record_text = "Patient demonstrates exceptional maneuverability in low-gravity environments." + mob_trait = TRAIT_FLUTTER + hardcore_value = -1 + icon = FA_ICON_PLANE + +/datum/quirk/flutter/add(client/client_source) + // Add movement element + quirk_holder.AddElementTrait(TRAIT_FLUTTER_MOVE, TRAIT_FLUTTER, /datum/element/flutter_move, FLUTTER_FUNCTIONAL_FORCE, FLUTTER_MIN_PRESSURE) + + // Add drifting trait + ADD_TRAIT(quirk_holder, TRAIT_NOGRAV_ALWAYS_DRIFT, TRAIT_FLUTTER) + +/datum/quirk/flutter/remove() + // Remove movement element + REMOVE_TRAIT(quirk_holder, TRAIT_FLUTTER_MOVE, TRAIT_FLUTTER) + + // Remove drifting trait + REMOVE_TRAIT(quirk_holder, TRAIT_NOGRAV_ALWAYS_DRIFT, TRAIT_FLUTTER) + +#undef FLUTTER_FUNCTIONAL_FORCE +#undef FLUTTER_MIN_PRESSURE diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/hallowed.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/hallowed.dm new file mode 100644 index 0000000000000..4c3192f78b674 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/hallowed.dm @@ -0,0 +1,76 @@ +/datum/quirk/hallowed + name = "Hallowed" + desc = "You have been blessed by a higher power, or otherwise become a deacon imbued with holy energy. Your divine presence gives you the power of a chaplain, and drives away unholy magics!" + value = 6 // Maybe up the cost if more is added later. + gain_text = span_notice("You feel holy energy radiating through your body.") + lose_text = span_notice("You feel your holy energy fading away...") + medical_record_text = "Patient is under the influence of an unidentified hallowed blessing. Please consult a chaplain." + mob_trait = TRAIT_HALLOWED + hardcore_value = -4 + icon = FA_ICON_CHURCH + mail_goodies = list ( + /obj/item/reagent_containers/cup/glass/bottle/holywater = 1 + ) + +/datum/quirk/hallowed/add(client/client_source) + // Give the holy trait. + ADD_TRAIT(quirk_holder, TRAIT_HOLY, TRAIT_HALLOWED) + + // Add status effect + quirk_holder.apply_status_effect(/datum/status_effect/quirk_examine/hallowed) + + // Register holy water interactions + RegisterSignal(quirk_holder, COMSIG_REAGENT_METABOLIZE_HOLYWATER, PROC_REF(metabolize_holywater)) + RegisterSignal(quirk_holder, COMSIG_REAGENT_PROCESS_HOLYWATER, PROC_REF(process_holywater)) + +/datum/quirk/hallowed/post_add() + // Makes the user holy. + quirk_holder.mind?.holy_role = HOLY_ROLE_DEACON + +/datum/quirk/hallowed/remove() + // Define quirk mob. + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove the holy trait. + REMOVE_TRAIT(quirk_mob, TRAIT_HOLY, TRAIT_HALLOWED) + + // Check if the holder is a deacon + if(quirk_holder.mind?.holy_role == HOLY_ROLE_DEACON) + // Revoke the holder's deacon status + quirk_mob.mind?.holy_role = NONE + + // Remove status effect + quirk_holder.remove_status_effect(/datum/status_effect/quirk_examine/hallowed) + + // Unregister holy water interactions + UnregisterSignal(quirk_holder, COMSIG_REAGENT_METABOLIZE_HOLYWATER) + UnregisterSignal(quirk_holder, COMSIG_REAGENT_PROCESS_HOLYWATER) + +/// Handle effects applied by metabolizing Holy Water +/datum/quirk/hallowed/proc/metabolize_holywater() + SIGNAL_HANDLER + + // Alert user of holy water effect. + to_chat(quirk_holder, span_nicegreen("The holy water nourishes you!")) + + // Add positive mood. + quirk_holder.add_mood_event("fav_food", /datum/mood_event/favorite_food) + +/// Handle effects applied by consuming Holy Water +/datum/quirk/hallowed/proc/process_holywater() + SIGNAL_HANDLER + + // Reduce disgust, hunger, and thirst + // These effects should be a foil to bloodfledge penalties + quirk_holder.adjust_disgust(-2) + quirk_holder.adjust_nutrition(6) + //quirk_holder.adjust_thirst(6) + +// Examine text status effect +/datum/status_effect/quirk_examine/hallowed + id = QUIRK_EXAMINE_HALLOWED + +// Set effect examine text +/datum/status_effect/quirk_examine/hallowed/get_examine_text() + return span_notice("[owner.p_They()] radiate[owner.p_s()] divine power.") + diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/hard_soles.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/hard_soles.dm new file mode 100644 index 0000000000000..ed2f236d6a621 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/hard_soles.dm @@ -0,0 +1,4 @@ +// Quirk was made free to replace the Invisifiber Footwraps +// This was the intention behind the item +/datum/quirk/hard_soles + value = 0 diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/radfiend.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/radfiend.dm new file mode 100644 index 0000000000000..78b2b0c284ad5 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/radfiend.dm @@ -0,0 +1,56 @@ +/datum/quirk/rad_fiend + name = "Rad Fiend" + desc = "You've been blessed by Cherenkov's warming light! Radiation is incapable of penetrating your protective barrier." + value = 6 + gain_text = span_notice("You feel empowered by Cherenkov's glow.") + lose_text = span_notice("You realize that rads aren't so rad.") + medical_record_text = "Patient emits a slight radioactive aura. The effect is harmless." + mob_trait = TRAIT_RAD_FIEND + hardcore_value = -2 + icon = FA_ICON_RADIATION + mail_goodies = list ( + /obj/item/geiger_counter = 1 + ) + +// This quirk does three things: +// - Immunity to radiation +// - A cosmetic glow effect +// - A favorite food mood bonus for drinking Nuka Cola + +/datum/quirk/rad_fiend/add(client/client_source) + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Add glow control action + var/datum/action/cosglow/update_glow/quirk_action = new + quirk_action.Grant(quirk_mob) + + // Add radiation immunity + ADD_TRAIT(quirk_mob, TRAIT_RADIMMUNE, TRAIT_RAD_FIEND) + + // Register reagent interactions + RegisterSignal(quirk_holder, COMSIG_REAGENT_METABOLIZE_NUKACOLA, PROC_REF(metabolize_nuka)) + +/datum/quirk/rad_fiend/remove() + // Define quirk holder mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove glow control action + var/datum/action/cosglow/update_glow/quirk_action = locate() in quirk_mob.actions + quirk_action.Remove(quirk_mob) + + // Remove glow effect + quirk_mob.remove_filter("rad_fiend_glow") + + // Remove radiation immunity + REMOVE_TRAIT(quirk_mob, TRAIT_RADIMMUNE, TRAIT_RAD_FIEND) + + // Unregister reagent interactions + UnregisterSignal(quirk_holder, COMSIG_REAGENT_METABOLIZE_NUKACOLA) + +/// Handle effects applied by consuming Nuka Cola +/datum/quirk/rad_fiend/proc/metabolize_nuka() + SIGNAL_HANDLER + + // Add mood bonus + quirk_holder.add_mood_event("fav_food", /datum/mood_event/favorite_food) diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/restorative_metabolism.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/restorative_metabolism.dm new file mode 100644 index 0000000000000..34b475ed12370 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/restorative_metabolism.dm @@ -0,0 +1,51 @@ +#define RESTMETA_BRUTE_THRESHOLD 50 +#define RESTMETA_BRUTE_AMOUNT -0.5 +#define RESTMETA_BURN_THRESHOLD 50 +#define RESTMETA_BURN_AMOUNT -0.25 +#define RESTMETA_TOX_THRESHOLD 50 +#define RESTMETA_TOX_AMOUNT -0.3 + +/datum/quirk/restorative_metabolism + name = "Restorative Metabolism" + desc = "Your body possesses a differentiated reconstructive ability, allowing you to slowly recover from light to moderate injuries. Critical injuries, wounds, and genetic damage will still require medical attention." + value = 10 + quirk_flags = QUIRK_PROCESSES + gain_text = span_notice("You feel a surge of reconstructive vitality coursing through your body...") + lose_text = span_notice("You sense your enhanced reconstructive ability fading away...") + medical_record_text = "Patient possesses a self-reconstructive condition. Medical care is only required under extreme circumstances." + mob_trait = TRAIT_RESTORATIVE_METABOLISM + hardcore_value = -10 + icon = FA_ICON_BRIEFCASE_MEDICAL + +/datum/quirk/restorative_metabolism/process(seconds_per_tick) + // Quirk holder must be injured + if(quirk_holder.health >= quirk_holder.maxHealth) + // Do nothing + return + + // Define health needing updates + var/need_mob_update = FALSE + + // Check brute threshold + if(quirk_holder.getBruteLoss() <= RESTMETA_BRUTE_THRESHOLD) + need_mob_update += quirk_holder.adjustBruteLoss(RESTMETA_BRUTE_AMOUNT, updating_health = FALSE) + + // Check burn threshold + if(quirk_holder.getFireLoss() <= RESTMETA_BURN_THRESHOLD) + need_mob_update += quirk_holder.adjustFireLoss(RESTMETA_BURN_AMOUNT, updating_health = FALSE) + + // Check tox threshold + if(quirk_holder.getToxLoss() <= RESTMETA_TOX_THRESHOLD) + need_mob_update += quirk_holder.adjustToxLoss(RESTMETA_TOX_AMOUNT, updating_health = FALSE, forced = TRUE) + + // Check if healing will be applied + if(need_mob_update) + // Update health + quirk_holder.updatehealth() + +#undef RESTMETA_BRUTE_THRESHOLD +#undef RESTMETA_BRUTE_AMOUNT +#undef RESTMETA_BURN_THRESHOLD +#undef RESTMETA_BURN_AMOUNT +#undef RESTMETA_TOX_THRESHOLD +#undef RESTMETA_TOX_AMOUNT diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/tough.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/tough.dm new file mode 100644 index 0000000000000..1bc1ea0a43a68 --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/tough.dm @@ -0,0 +1,21 @@ +/datum/quirk/tough + name = "Tough" + desc = "Your body is abnormally enduring, granting you 10% more health." + value = 4 + gain_text = span_notice("You feel very sturdy.") + lose_text = span_notice("You feel less sturdy.") + medical_record_text = "Patient has an abnormally high capacity for injury." + mob_trait = TRAIT_TOUGH + hardcore_value = -2 + icon = FA_ICON_SHIELD_HEART + +/datum/quirk/tough/add(client/client_source) + quirk_holder.maxHealth *= 1.1 + +/datum/quirk/tough/remove() + if(!quirk_holder) + return + quirk_holder.maxHealth *= 0.909 //close enough + + + diff --git a/modular_zzplurt/code/datums/quirks/positive_quirks/vacuum_resistance.dm b/modular_zzplurt/code/datums/quirks/positive_quirks/vacuum_resistance.dm new file mode 100644 index 0000000000000..da609adbdb03a --- /dev/null +++ b/modular_zzplurt/code/datums/quirks/positive_quirks/vacuum_resistance.dm @@ -0,0 +1,27 @@ +/datum/quirk/vacuum_resistance + name = "Vacuum Resistance" + desc = "Your body is specially adapted to withstand and operate in zero-pressure environments. You may still need a source of breathable air, however." + value = 8 + gain_text = span_notice("Your physique attunes to the silence of space, now able to operate in zero pressure.") + lose_text = span_notice("Your physiology reverts as your space faring gifts lay dormant once more.") + medical_record_text = "Patient's body has fully adapted to zero-pressure environments." + mob_trait = TRAIT_VACUUM_RESIST + hardcore_value = -6 + icon = FA_ICON_ROCKET + mail_goodies = list ( + /obj/item/storage/box/emergency_spacesuit = 1 + ) + +/datum/quirk/vacuum_resistance/add(client/client_source) + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Add quirk traits + ADD_TRAIT(quirk_mob,TRAIT_RESISTLOWPRESSURE,TRAIT_VACUUM_RESIST) + +/datum/quirk/vacuum_resistance/remove() + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = quirk_holder + + // Remove quirk traits + REMOVE_TRAIT(quirk_mob,TRAIT_RESISTLOWPRESSURE,TRAIT_VACUUM_RESIST) diff --git a/modular_zzplurt/code/datums/status_effects/quirk_examine.dm b/modular_zzplurt/code/datums/status_effects/quirk_examine.dm new file mode 100644 index 0000000000000..7a707c29b2f8b --- /dev/null +++ b/modular_zzplurt/code/datums/status_effects/quirk_examine.dm @@ -0,0 +1,9 @@ +/// Status effect subtype used for applying examine text on quirks +/datum/status_effect/quirk_examine/ + id = "quirk_examine_base" + // Unlimited duration + duration = -1 + // Do not tick + tick_interval = -1 + // Do not show an alert + alert_type = null diff --git a/modular_zzplurt/code/datums/weather/weather_types/ash_storm.dm b/modular_zzplurt/code/datums/weather/weather_types/ash_storm.dm new file mode 100644 index 0000000000000..5ab0a2965b227 --- /dev/null +++ b/modular_zzplurt/code/datums/weather/weather_types/ash_storm.dm @@ -0,0 +1,12 @@ +// Lavaland ash storms +/datum/weather/ash_storm/weather_act(mob/living/victim) + // Check for resistance quirk + if(HAS_TRAIT(victim, TRAIT_ASHRESISTANCE)) + // Do stamina damage instead + victim.adjustStaminaLoss(4) + + // No other effects + return + + // Run normally + . = ..() diff --git a/modular_zzplurt/code/game/objects/items/cards_ids.dm b/modular_zzplurt/code/game/objects/items/cards_ids.dm new file mode 100644 index 0000000000000..d7062bf403466 --- /dev/null +++ b/modular_zzplurt/code/game/objects/items/cards_ids.dm @@ -0,0 +1,40 @@ +// Base quirk ID card +/obj/item/card/id/advanced/quirk + name = "quirky ID card" + desc = "Show this to a coder to indicate something went wrong." + assignment = "Error Reporter" + trim = /datum/id_trim/quirk + +// Quirk: Bloodsucker Fledgling +/obj/item/card/id/advanced/quirk/bloodfledge + name = "bloodfledge identification card" + desc = "An ID made to easily recognize sanguine crew members without requiring medical scans." + icon = 'modular_zzplurt/icons/obj/card.dmi' + icon_state = "card_bloodfledge" + assignment = "Bloodsucker Fledgling" + trim = /datum/id_trim/quirk/bloodfledge + +// Quirk: Concubus +/obj/item/card/id/advanced/quirk/concubus + name = "concubus identification card" + trim = /datum/id_trim/quirk/concubus + +// Quirk: Gargoyle +/obj/item/card/id/advanced/quirk/gargoyle + name = "gargoyle identification card" + trim = /datum/id_trim/quirk/gargoyle + +// Quirk: Hallowed +/obj/item/card/id/advanced/quirk/hallowed + name = "deacon identification card" + trim = /datum/id_trim/quirk/hallowed + +// Quirk: Rad Fiend +/obj/item/card/id/advanced/quirk/rad_fiend + name = "rad fiend identification card" + trim = /datum/id_trim/quirk/rad_fiend + +// Quirk: Werewolf +/obj/item/card/id/advanced/quirk/werewolf + name = "werewolf identification card" + trim = /datum/id_trim/quirk/werewolf diff --git a/modular_zzplurt/code/game/objects/items/emags.dm b/modular_zzplurt/code/game/objects/items/emags.dm new file mode 100644 index 0000000000000..c23ee23f7a05a --- /dev/null +++ b/modular_zzplurt/code/game/objects/items/emags.dm @@ -0,0 +1,184 @@ +#define BLOODMAG_USE_COST (BLOOD_VOLUME_NORMAL * 0.10) // 10% of standard blood +#define BLOODMAG_USE_COST_FAKE (BLOOD_VOLUME_NORMAL * 0.01) // 1% of standard blood +#define BLOODMAG_CD_TIME 3 SECONDS + +// +// Bloodfledge exclusive emag +// +/obj/item/card/emag/bloodfledge + name = "hemorrhagic sanguinizer" + desc = "A card with a blood seal attached to some circuitry. Requires special training to use properly." + icon = 'modular_zzplurt/icons/obj/card.dmi' + icon_state = "bloodmag" + // List of allowed types - Unused! + //var/type_whitelist + + // Can this actually emag things? + var/unlocked = FALSE + + // Cooldown between uses + COOLDOWN_DECLARE(bloodmag_cooldown) + +// On creation +// Unused feature: type whitelist +/* +/obj/item/card/emag/bloodfledge/Initialize(mapload) + . = ..() + + // List of object types this emag can affect + type_whitelist = list( + typesof(/obj/machinery/chem_dispenser), // Unlocks more reagents + typesof(/obj/structure/bodycontainer/morgue), // Changes status light + ) +*/ + +// Emag the bloodmag +/obj/item/card/emag/bloodfledge/emag_act(mob/user, obj/item/card/emag/emag_card) + // Check if valid + if(isnull(user) || !istype(emag_card)) + return FALSE + + // Check if already unlocked + if(unlocked) + // Alert user and return + balloon_alert(user, "already upgraded!") + return FALSE + + // Unlock full functionality + unlocked = TRUE + + // Alert user + balloon_alert(user, "sanguinizer upgraded!") + +// Examine text +/obj/item/card/emag/bloodfledge/examine(mob/user) + . = ..() + + // Check for bloodfledge + if(HAS_TRAIT(user, TRAIT_BLOODFLEDGE)) + // Check if unlocked + if(unlocked) + // Add unlocked text + . += span_notice("It's been switched to hemorrhagic sequencing mode.") + + // Item is not unlocked + else + // Add fake usage text + . += span_notice("You know exactly how to sanguinize things with this!") + +// Can use check +/obj/item/card/emag/bloodfledge/can_emag(atom/target, mob/user) + . = ..() + + // Check parent return + if(!.) + return FALSE + + // Check for bloodfledge + if(!HAS_TRAIT(user, TRAIT_BLOODFLEDGE)) + // Warn user and return + to_chat(user, span_warning("You can't figure out what to do with [src]. Maybe you should ask someone more qualified.")) + return FALSE + + // Check for bloodless + if(HAS_TRAIT(user, TRAIT_NOBLOOD)) + // Warn user and return + to_chat(user, span_warning("Your body is incapable of utilizing [src]!")) + return FALSE + + // Check cooldown + if(!COOLDOWN_FINISHED(src, bloodmag_cooldown)) + // Warn user and return + balloon_alert(user, "still recharging!") + return FALSE + + // Check for unlocked state + if(!unlocked) + // Give false alerts + user.visible_message(span_danger("[user] sanguinizes [target] with [user.p_their()] [src]! Never before has [target] been more sanguine!"), span_warning("You sanguinize [target]! Never before has [target] been more sanguine!")) + balloon_alert(user, "target sanguinized!") + + // Start cooldown to prevent spam + COOLDOWN_START(src, bloodmag_cooldown, BLOODMAG_CD_TIME) + + // Apply color named "Sanguine" + //target.add_atom_colour("#6D0C0D", WASHABLE_COLOUR_PRIORITY) + + // Define carbon holder + var/mob/living/carbon/item_mob = user + + // Check if holder exists + if(item_mob) + // Cause user to bleed + item_mob.bleed(BLOODMAG_USE_COST_FAKE) + + // Apply blood to the target + target.add_mob_blood(item_mob) + + // Return failure + return FALSE + + // Allow use + return TRUE + + // Unused feature: type whitelist + /* + // Check for compatible object type + for (var/list/subtypelist in type_whitelist) + if (target.type in subtypelist) + return TRUE + + // Interaction is not valid + // Warn user and return + balloon_alert(user, "incompatible target!") + return FALSE + */ + +// Trigger on using +/obj/item/card/emag/bloodfledge/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + . = ..() + + // Check if successful + if(. != ITEM_INTERACT_SUCCESS) + // Do nothing + return + + // Start cooldown + COOLDOWN_START(src, bloodmag_cooldown, BLOODMAG_CD_TIME) + + // Define carbon holder + var/mob/living/carbon/item_mob = user + + // Check if holder exists + if(item_mob) + // Cause target to bleed + item_mob.bleed(BLOODMAG_USE_COST) + + // Apply blood to the target + interacting_with.add_mob_blood(item_mob) + + // Warn user + to_chat(user, span_boldwarning("You can feel [src] draining your life force.")) + +// Suicide action +/obj/item/card/emag/bloodfledge/suicide_act(mob/living/carbon/user) + // Alert in chat + user.visible_message(span_suicide("[user] hacks themselves with [src], unlocking ultra gore mode!")) + + // Gib the user + user.gib(DROP_ALL_REMAINS) + + // Check if unlocked + if(unlocked) + // Explode + explosion(src, light_impact_range = 1, explosion_cause = src) + + // Delete item + qdel(src) + + // Return success + return MANUAL_SUICIDE + +#undef BLOODMAG_USE_COST +#undef BLOODMAG_USE_COST_FAKE +#undef BLOODMAG_CD_TIME diff --git a/modular_zzplurt/code/game/objects/items/implants/implant_disrobe.dm b/modular_zzplurt/code/game/objects/items/implants/implant_disrobe.dm new file mode 100644 index 0000000000000..592039c4905d0 --- /dev/null +++ b/modular_zzplurt/code/game/objects/items/implants/implant_disrobe.dm @@ -0,0 +1,67 @@ +/obj/item/implant/disrobe + name = "rapid disrobe implant" + icon = 'icons/obj/clothing/under/color.dmi' + icon_state = "jumpsuit" + + // Custom action for extra customization + actions_types = list(/datum/action/item_action/disrobe) + +// Implant flavor text +/obj/item/implant/disrobe_ultra/get_data() + // Define text variable + var/dat = {" + Implant Specifications:
+ Name: Rapid Disrobe Implant
+ Important Notes: Nanotransen requires employees to follow workplace safety standards, including protective clothing.
+
+ Implant Details:
+ Function: Lines the subject's epidermis with high power static charge nodes, allowing the rapid removal of worn clothing.
+ Special Features: Will not cause drunkenness in most subjects.
+ Integrity: Implant will last so long as it is inside the host. + "} + + // Return text + return dat + +// Only allow use on "human" targets +/obj/item/implant/disrobe/can_be_implanted_in(mob/living/target) + if(!ishuman(target)) + return FALSE + . = ..() + +// Runs on toggling the implant +/obj/item/implant/disrobe/activate() + . = ..() + + // Define target + var/mob/living/carbon/target_user = imp_in + + // Perform LPD effect + target_user.clothing_burst(TRUE) + +/* + * Action datums +*/ + +// Action for rapid wardrobe removal +/datum/action/item_action/disrobe + name = "Rapid Disrobe" + desc = "Instantly eject all covering clothing from your body." + button_icon = 'modular_zzplurt/icons/mob/actions/misc_actions.dmi' + button_icon_state = "no_uniform" + background_icon_state = "bg_tech" + +/* + * Implant items +*/ + +// Implanter item +/obj/item/implanter/disrobe + name = "implanter (rapid disrobe)" + imp_type = /obj/item/implant/disrobe + +// Implant case item +/obj/item/implantcase/disrobe + name = "implant case - 'rapid disrobe'" + desc = "A glass case containing a rapid disrobe implant." + imp_type = /obj/item/implant/disrobe diff --git a/modular_zzplurt/code/game/objects/items/implants/implant_gfluid.dm b/modular_zzplurt/code/game/objects/items/implants/implant_gfluid.dm new file mode 100644 index 0000000000000..7a3cc543be4fd --- /dev/null +++ b/modular_zzplurt/code/game/objects/items/implants/implant_gfluid.dm @@ -0,0 +1,157 @@ +/* Commented until we have the genital fluid options +/obj/item/implant/genital_fluid + name = "genital fluid implant" + icon = 'modular_zzplurt/icons/obj/implants.dmi' + icon_state = "genital_fluid" + var/use_blacklist = TRUE + + // Custom action for extra customization + actions_types = list(/datum/action/item_action/genital_fluid_infuse) + +// Implant flavor text +/obj/item/implant/genital_fluid/get_data() + var/dat = {"Implant Specifications:
+ Name: Nanotrasen Genital Fluid Inducer Implant
+ Important Notes: It will only works in genitals that have the natural function of producing fluids.
+
+ Implant Details:
+ Function: Allows the user to induce their genitals into producing a specific reagent.
+ Special Features: Will prevent harmful liquids from being accepted as a genital fluid replace.
+ Integrity: Implant will last so long as the implant is inside the host and they possess genitals capable of fluid production."} + return dat + +// Only allow use on "human" targets +/obj/item/implant/genital_fluid/can_be_implanted_in(mob/living/target) + if(!ishuman(target)) + return FALSE + . = ..() + +// Runs on toggling the implant +/obj/item/implant/genital_fluid/activate() + . = ..() + + // Set list of possible genitals + var/list/obj/item/organ/genital/genitals_list + + // Set list of possible fluids + var/list/datum/reagent/fluid_list = list() + + // Set owner + var/mob/living/carbon/human/genital_owner = imp_in + + // List their genitals if they have any at all + for(var/obj/item/organ/genital/genital_checked in genital_owner.internal_organs) + if(istype(genital_checked) && (genital_checked.genital_flags & GENITAL_FUID_PRODUCTION)) + // Add genitals to the list + LAZYADD(genitals_list, genital_checked) + + // List their current reagents if they're valid + for(var/datum/reagent/genital_reagent in genital_owner.reagents.reagent_list) + if((find_reagent_object_from_type(genital_reagent.type)) && ((genital_reagent.type in allowed_gfluid_paths()) || !use_blacklist)) + // Add valid reagents to the list + LAZYADD(fluid_list, find_reagent_object_from_type(genital_reagent.type)) + + // List any reagents they may be holding in their hands + if(genital_owner.available_rosie_palms(TRUE, /obj/item/reagent_containers)) + for(var/obj/item/reagent_containers/container in genital_owner.held_items) + if(container.is_open_container() || istype(container, /obj/item/reagent_containers/food/snacks)) + for(var/datum/reagent/genital_reagent in container.reagents.reagent_list) + if((find_reagent_object_from_type(genital_reagent.type)) && ((genital_reagent.type in allowed_gfluid_paths()) || !use_blacklist)) + // Add valid reagents to the list + LAZYADD(fluid_list, find_reagent_object_from_type(genital_reagent.type)) + + // Return if genitals list is void/null + if(!genitals_list) + // Play an error sound + SEND_SOUND(genital_owner, 'sound/machines/terminal_error.ogg') + + // Alert the user in chat + to_chat(genital_owner, span_notice("ERROR: No compatible genitals detected.")) + + // Escape + return + + // Prompt user for which genital to use + var/obj/item/organ/genital/genital_input = tgui_input_list(genital_owner, "Pick a genital", "Genital Fluid Infuser", genitals_list) + if(!genital_input) + // No selection was made + return + + // Update list of possible fluids + fluid_list = list(find_reagent_object_from_type(genital_input.get_default_fluid())) + fluid_list + + // Prompt user to select a new fluid + var/datum/reagent/reagent_selection = tgui_input_list(genital_owner, "Choose your new reagent", "Genital Fluid Infuser", fluid_list) + if(!reagent_selection) + // No selection was made + return + + // Set new fluid + genital_input.fluid_id = reagent_selection.type + + // Play the reagent processing sound effect + SEND_SOUND(genital_owner, 'sound/effects/bubbles.ogg') + + // Display flavor text + to_chat(genital_owner, span_notice("You feel the fluids inside your [genital_input.name] bubble and swirl...")) + + // Send admin notice + message_admins("[ADMIN_LOOKUPFLW(genital_owner)] changed the fluid of [genital_owner.p_their()] [genital_input.name] to [reagent_selection].") + +// Unlock with an emag +/obj/item/implant/genital_fluid/emag_act() + . = ..() + name = "hacked genital fluid implant" + use_blacklist = FALSE + obj_flags |= EMAGGED + +/* + * Action datum +*/ + +// Action for updating genital fluids +/datum/action/item_action/genital_fluid_infuse + name = "Infuse Genital Fluids" + desc = "Activate an integrated reagent receptor device to modify your genital contents." + icon_icon = 'modular_zzplurt/icons/obj/implants.dmi' + button_icon_state = "genital_fluid" + background_icon_state = "bg_tech" + + // Restrict non-synthesizable reagents? + var/use_blacklist = TRUE + +/* + * Implant items +*/ + +// Implanter item +/obj/item/implanter/genital_fluid + name = "implanter (genital fluid)" + imp_type = /obj/item/implant/genital_fluid + +// Implanter item, emagged +/obj/item/implanter/genital_fluid/hacked + name = "implanter (genital fluid (hacked))" + +/obj/item/implanter/genital_fluid/hacked/New() + . = ..() + if(istype(imp, /obj/item/implant/genital_fluid)) + var/obj/item/implant/genital_fluid/I = imp + I.emag_act() + +// Implant case item +/obj/item/implantcase/genital_fluid + name = "implant case - 'Genital Fluid'" + desc = "A glass case containing a Genital Fluid Infuser implant." + imp_type = /obj/item/implant/genital_fluid + +// Implant case item, emagged +/obj/item/implantcase/genital_fluid/hacked + name = "implant case - 'Genital Fluid (Hacked)'" + +/obj/item/implantcase/genital_fluid/hacked/New() + . = ..() + if(istype(imp, /obj/item/implant/genital_fluid)) + var/obj/item/implant/genital_fluid/I = imp + I.emag_act() +*/ diff --git a/modular_zzplurt/code/game/objects/items/implants/implant_hide_backpack.dm b/modular_zzplurt/code/game/objects/items/implants/implant_hide_backpack.dm new file mode 100644 index 0000000000000..ff8078866b150 --- /dev/null +++ b/modular_zzplurt/code/game/objects/items/implants/implant_hide_backpack.dm @@ -0,0 +1,117 @@ +/obj/item/implant/hide_backpack + name = "storage concealment implant" + desc = "Prevents your backpack from being observable by the naked eye." + icon = 'modular_zzplurt/icons/obj/storage.dmi' + icon_state = "backpack_faded" + + // Custom action for extra customization + actions_types = list(/datum/action/item_action/hide_backpack) + +// Implant flavor text +/obj/item/implant/hide_backpack/get_data() + var/dat = {"Implant Specifications:
+ Name: Chameleon Storage Concealment Implant
+ Important Notes: Pending approval from some security teams.
+
+ Implant Details:
+ Function: Subject emits a weak psychic signal that conceals the presence of back-equipped gear items.
+ Integrity: Implant will last so long as the implant is inside the host."} + return dat + +// Only allow use on "human" targets +/obj/item/implant/hide_backpack/can_be_implanted_in(mob/living/target) + if(!ishuman(target)) + return FALSE + . = ..() + +/* + * Action datum +*/ + +// Action for toggling backpack +/datum/action/item_action/hide_backpack + name = "Conceal Backwear" + desc = "Toggle a weak psychic signal that conceals the presence of back-equipped gear items." + button_icon = 'modular_zzplurt/icons/obj/storage.dmi' + button_icon_state = "backpack_faded" + background_icon_state = "bg_tech" + + // Is this being used? + var/ability_active = TRUE + +// Runs on gaining the ability +/datum/action/item_action/hide_backpack/Grant(mob/grant_to) + . = ..() + + // Add the trait + adjust_trait(TRUE) + +// Runs on losing the ability +/datum/action/item_action/hide_backpack/Remove(mob/remove_from) + // Remove the trait (must be done before removal so that owner still exists) + adjust_trait(FALSE) + return ..() + +// Function to update trait +/datum/action/item_action/hide_backpack/proc/adjust_trait(state) + // Set owner + var/mob/living/carbon/human/action_owner = owner + + // Toggle trait status + if(state) + ADD_TRAIT(action_owner, TRAIT_HIDE_BACKPACK, TRAIT_GENERIC) + else + REMOVE_TRAIT(action_owner, TRAIT_HIDE_BACKPACK, TRAIT_GENERIC) + + // Update sprites + action_owner.update_worn_back() + +// Runs on toggling the ability +/datum/action/item_action/hide_backpack/Trigger(trigger_flags) + // Set owner + var/mob/living/carbon/human/action_owner = owner + + // Update active status + ability_active = !ability_active + + // Update trait + adjust_trait(ability_active) + + // Set icon state variables + if(ability_active) + // Half-faded backpack + button_icon = 'modular_zzplurt/icons/obj/storage.dmi' + button_icon_state = "backpack_faded" + else + // Normal backpack + button_icon = 'icons/obj/storage/backpack.dmi' + button_icon_state = "backpack" + + // Update icon state + build_all_button_icons() + + // Display fake sparks to match flavor text + // Temporarily removed pending re-implementation + //do_fake_sparks(2, FALSE, action_owner) + + // Set toggle text based on active state + var/implant_toggle_text = (!ability_active ? "discernible" : "imperceptible") + var/implant_toggle_text_2 = (!ability_active ? "dis" : "") // Engage or Disengage + + // Display a chat message + action_owner.visible_message(span_notice("The equipment worn on [action_owner]'s back-region flickers momentarily, before becoming [implant_toggle_text]."), span_notice("You [implant_toggle_text_2]engage the Storage Concealment Implant, causing your backpack to be [implant_toggle_text].")) + +/* + * Implant items +*/ + +// Implanter item +/obj/item/implanter/hide_backpack + name = "implanter (storage concealment)" + imp_type = /obj/item/implant/hide_backpack + +// Implant case item +/obj/item/implantcase/hide_backpack + name = "implant case - 'Storage Concealment'" + desc = "A glass case containing a Storage Concealment implant." + imp_type = /obj/item/implant/hide_backpack diff --git a/modular_zzplurt/code/modules/clothing/masks/gasmask.dm b/modular_zzplurt/code/modules/clothing/masks/gasmask.dm new file mode 100644 index 0000000000000..6efb07f567984 --- /dev/null +++ b/modular_zzplurt/code/modules/clothing/masks/gasmask.dm @@ -0,0 +1,5 @@ +/obj/item/clothing/mask/gas/cosmetic + name = "aesthetic gas mask" + desc = "A face-covering mask that resembles a traditional gas mask, but without the breathing functionality." + clothing_flags = NONE + armor_type = /datum/armor/none diff --git a/modular_zzplurt/code/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_tongue.dm b/modular_zzplurt/code/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_tongue.dm new file mode 100644 index 0000000000000..6bcaf81ec0b80 --- /dev/null +++ b/modular_zzplurt/code/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_tongue.dm @@ -0,0 +1,3 @@ +/datum/component/organ_corruption/tongue + // Replace with new bite type + tongue_action_type = /datum/action/cooldown/bloodfledge/bite/corrupted_tongue diff --git a/modular_zzplurt/code/modules/mob/living/carbon/carbon.dm b/modular_zzplurt/code/modules/mob/living/carbon/carbon.dm new file mode 100644 index 0000000000000..ffd9ebfde673d --- /dev/null +++ b/modular_zzplurt/code/modules/mob/living/carbon/carbon.dm @@ -0,0 +1,37 @@ +// Liquid Panty Dropper effect +/mob/living/carbon/proc/clothing_burst(throw_clothes = FALSE) + // Variable for if the action succeeded + var/user_disrobed = FALSE + + // Get worn items + var/items = get_contents() + + // Iterate over worn items + for(var/obj/item/item_worn in items) + // Ignore non-mob (storage) + if(!ismob(item_worn.loc)) + continue + + // Ignore held items + if(is_holding(item_worn)) + continue + + // Check for anything covering a body part + if(item_worn.body_parts_covered) + // Set the success variable + user_disrobed = TRUE + + // Drop the target item + dropItemToGround(item_worn, TRUE) + + // Throw item to a random spot + if(throw_clothes) + item_worn.throw_at(pick(oview(7,get_turf(src))),10,1) + + // When successfully disrobing a target + if(user_disrobed) + // Display a chat message + visible_message(span_userlove("[src] suddenly bursts out of [p_their()] clothes!"), span_userlove("You suddenly burst out of your clothes!")) + + // Play the ripped poster sound + playsound(loc, 'sound/items/poster/poster_ripped.ogg', 50, 1) diff --git a/modular_zzplurt/code/modules/mob/living/carbon/carbon_defense.dm b/modular_zzplurt/code/modules/mob/living/carbon/carbon_defense.dm new file mode 100644 index 0000000000000..ee23011e86814 --- /dev/null +++ b/modular_zzplurt/code/modules/mob/living/carbon/carbon_defense.dm @@ -0,0 +1,234 @@ +#define ASS_SLAP_EXTRA_RANGE -1 +#define BADTOUCH_RETALIATE_CHANCE 5 +#define BADTOUCH_RETALIATE_DAMAGE 5 +#define BADTOUCH_RETALIATE_KNOCKDOWN 20 +#define BADTOUCH_RETALIATE_SADISM_MULT 2 +#define ASS_JIGGLE_COOLDOWN 10 SECONDS +#define ASS_JIGGLE_STAMLOSS 25 +#define ASS_JIGGLE_VERBS_1 pick("rippling","jiggling","sloshing","clapping","wobbling") +#define ASS_JIGGLE_VERBS_2 pick("ripples","jiggles","sloshes","claps","wobbles") + +/mob/living/carbon/disarm(mob/living/carbon/target) + // Check if targeting the groin + if(!(zone_selected == BODY_ZONE_PRECISE_GROIN && target.dir == dir)) + // Nothing to do here! + // Run original + . = ..() + return + + // Check for personal space + if(HAS_TRAIT(target, TRAIT_PERSONALSPACE)) + // Attempt to retaliate + // No check, because this is autonomous + target.do_touch_retaliate(src) + + // Play reflect sound + playsound(target.loc, get_sfx_skyrat(SFX_BULLET_IMPACT_METAL), 50, TRUE, ASS_SLAP_EXTRA_RANGE) + + // Display messages + visible_message( + span_danger("[src] tries to slap [target]'s ass, but [src.p_their()] hand bounces off it like solid steel!"), + span_danger("You try to slap [target]'s ass, but your hand bounces off it like solid steel!"), + "You hear a loud clang!", + ignored_mobs = list(target) + ) + to_chat(target, span_danger("[src] tries to slap your ass, but [src.p_their()] hand bounces off it!")) + + // End here + return + + // Check for slap request + if(HAS_TRAIT(target, TRAIT_JIGGLY_ASS)) + // Get target quirk + var/datum/quirk/jiggly_ass/quirk_jiggle = target.get_quirk(/datum/quirk/jiggly_ass) + + // Check for cooldown + if(!COOLDOWN_FINISHED(quirk_jiggle, wiggle_cooldown)) + // Display message + target.visible_message(span_warning("[target]'s big butt is still [ASS_JIGGLE_VERBS_1] about way too much to get a good smack!!"), \ + span_boldwarning("[src] tries to smack your jiggly ass, but can't get a lock on it!")) + + // End here + return + + // Cooldown is not active + // Start the cooldown + COOLDOWN_START(quirk_jiggle, wiggle_cooldown, ASS_JIGGLE_COOLDOWN) + + // Add mood bonuses + add_mood_event(QMOOD_JIGGLY_ASS, /datum/mood_event/butt_slap) + target.add_mood_event(QMOOD_JIGGLY_ASS, /datum/mood_event/butt_slapped) + + // Reduce target stamina (???) + target.adjustStaminaLoss(ASS_JIGGLE_STAMLOSS) + + // Display message + visible_message( + span_purple("[src] slaps [target] right on the ass, and sends it [ASS_JIGGLE_VERBS_1]!"), + span_purple("You slap [target] on the ass, and send it [ASS_JIGGLE_VERBS_1]! How satisfying!"), + "You hear a slap.", + ignored_mobs = list(target) + ) + to_chat(target, span_purple("[src] smacks your big fat butt, and sends it [ASS_JIGGLE_VERBS_1]! It [ASS_JIGGLE_VERBS_2] about and throws you off balance!")) + + // Apply effects + do_ass_slap_animation(target) + conditional_pref_sound(target.loc, 'sound/items/weapons/slap.ogg', 50, TRUE, ASS_SLAP_EXTRA_RANGE) + + // Run original + . = ..() + +/mob/living/carbon/adjustOxyLoss(amount, updating_health = TRUE, forced, required_biotype, required_respiration_type) + . = ..() + + // Check parent return + if(!.) + return + + // Check for Choke Slut + if(HAS_TRAIT(src, TRAIT_CHOKE_SLUT)) + // Check if amount is positive + // Negative values remove suffocation + if(amount < 0) + return + + // Check if alive and ERP is enabled + if(stat >= DEAD || !client?.prefs?.read_preference(/datum/preference/toggle/erp)) + return + + // Check for no breathing + if(HAS_TRAIT(src, TRAIT_NOBREATH)) + return + + // Define quirk mob + var/mob/living/carbon/human/quirk_mob = src + + // Check if quirk mob exists + if(!quirk_mob) + return + + // Adjust arousal + quirk_mob.adjust_arousal(amount) + +/mob/living/carbon/proc/can_touch_retaliate() + // User must be conscious + if(src.stat != CONSCIOUS) + // Do nothing + return FALSE + + // User cannot be handcuffed + if(src.handcuffed) + return FALSE + + // User cannot be a pacifist + if(HAS_TRAIT(src, TRAIT_PACIFISM)) + return FALSE + + // User can retaliate + return TRUE + +/mob/living/carbon/proc/do_touch_retaliate(mob/living/carbon/toucher) + // Check if toucher enjoys this + if(HAS_TRAIT(toucher, TRAIT_MASOCHISM)) + // Cause toucher to express contentment + toucher.try_lewd_autoemote("moan") + + // Add good mood event + toucher.add_mood_event(QMOOD_BADTOUCH_VICTIM, /datum/mood_event/badtouch_retaliate/victim_good) + + // They don't enjoy this + else + // Cause toucher to scream + toucher.emote("scream") + + // Add bad mood event + toucher.add_mood_event(QMOOD_BADTOUCH_VICTIM, /datum/mood_event/badtouch_retaliate/victim_bad) + + // Damage amount to apply + var/retaliate_damage = BADTOUCH_RETALIATE_DAMAGE + + // Check if target is a sadist + if(HAS_TRAIT(src, TRAIT_SADISM)) + // Multiply retaliation damage + retaliate_damage *= BADTOUCH_RETALIATE_SADISM_MULT + + // Add good mood event + src.add_mood_event(QMOOD_BADTOUCH_ATTACKER, /datum/mood_event/badtouch_retaliate/attacker_good) + + // Target is not a sadist + else + // Add bad mood event + src.add_mood_event(QMOOD_BADTOUCH_ATTACKER, /datum/mood_event/badtouch_retaliate/attacker_bad) + + // Determine toucher's hand + var/which_hand = BODY_ZONE_PRECISE_L_HAND + if(!(toucher.active_hand_index % 2)) + which_hand = BODY_ZONE_PRECISE_R_HAND + + // Apply damage to toucher's hand + toucher.apply_damage(retaliate_damage, BRUTE, which_hand) + + // Log interaction + log_combat(src, toucher, "retaliates against", "automatically due to a no-touching trait.") + + // Return success + return TRUE + +// Proc to recreate old Distant quirk +/mob/living/carbon/proc/badtouch_retaliate(mob/living/carbon/toucher) + // Check for activation chance + if(!prob(BADTOUCH_RETALIATE_CHANCE)) + // Do nothing! + return + + // Check if this is allowed + if(!can_touch_retaliate()) + // Do nothing + return + + // Attempt to retaliate + src.do_touch_retaliate(toucher) + + // Play attack sound + playsound(get_turf(src), 'sound/effects/wounds/crack1.ogg', 50, 1, -1) + + // Display message + toucher.visible_message(span_warning("[src] twists [toucher]\'s arm in retaliation for touching [p_them()]!"), \ + span_boldwarning("Your arm gets twisted in [src]\'s grasp!")) + + // Drop toucher's held item + toucher.dropItemToGround(toucher.get_active_held_item()) + + // Knock down toucher + toucher.Knockdown(BADTOUCH_RETALIATE_KNOCKDOWN) + +// Bad Touch retaliate mood events +/datum/mood_event/badtouch_retaliate/attacker_bad + description = span_danger("Someone got hurt because of me.") + mood_change = -2 + timeout = 2 MINUTES + +/datum/mood_event/badtouch_retaliate/attacker_good + description = span_purple("I taught someone a lesson.") + mood_change = 2 + timeout = 2 MINUTES + +/datum/mood_event/badtouch_retaliate/victim_bad + description = span_danger("I shouldn't touch people without permission.") + mood_change = -2 + timeout = 2 MINUTES + +/datum/mood_event/badtouch_retaliate/victim_good + description = span_purple("I deserved that for what I did.") + mood_change = 2 + timeout = 2 MINUTES + +#undef ASS_SLAP_EXTRA_RANGE +#undef BADTOUCH_RETALIATE_CHANCE +#undef BADTOUCH_RETALIATE_DAMAGE +#undef BADTOUCH_RETALIATE_KNOCKDOWN +#undef BADTOUCH_RETALIATE_SADISM_MULT +#undef ASS_JIGGLE_COOLDOWN +#undef ASS_JIGGLE_STAMLOSS +#undef ASS_JIGGLE_VERBS_1 +#undef ASS_JIGGLE_VERBS_2 diff --git a/modular_zzplurt/code/modules/mob/living/carbon/human/human_update_icons.dm b/modular_zzplurt/code/modules/mob/living/carbon/human/human_update_icons.dm index 9f1c005e96a36..2cda6ba96951c 100644 --- a/modular_zzplurt/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/modular_zzplurt/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -382,6 +382,20 @@ update_body_parts() +// Function for updating back sprites +/mob/living/carbon/human/update_worn_back() + . = ..() + + // Check for hidden backpack trait + if(HAS_TRAIT(src, TRAIT_HIDE_BACKPACK)) + // Define back overlays + var/mutable_appearance/back_overlay = overlays_standing[BACK_LAYER] + + // Check for existing overlay + if(back_overlay) + // Remove overlays + remove_overlay(BACK_LAYER) + /mob/living/carbon/human/proc/update_hud_shirt(obj/item/worn_item) worn_item.screen_loc = ui_shirt if((client && hud_used) && (hud_used.inventory_shown && hud_used.hud_shown && hud_used.extra_shown)) diff --git a/modular_zzplurt/code/modules/mob/living/carbon/human/species.dm b/modular_zzplurt/code/modules/mob/living/carbon/human/species.dm index fa5446c919c8c..686c927f56d2d 100644 --- a/modular_zzplurt/code/modules/mob/living/carbon/human/species.dm +++ b/modular_zzplurt/code/modules/mob/living/carbon/human/species.dm @@ -8,6 +8,24 @@ LAZYADD(offset_features, extra_offset_features) . = ..() +// Radiation handling +// Currently unused +/* +/datum/species/handle_radiation(mob/living/carbon/human/target_mob) + // Check for Rad Fiend quirk + if(HAS_TRAIT(target_mob, TRAIT_RAD_FIEND)) + // Define radiation component + var/datum/component/irradiated/rad_effect = target_mob.GetComponent(/datum/component/irradiated) + + // Check if time threshold is met + if(rad_effect?.beginning_of_irradiation < RADFIEND_IMMUNITY_TIME) + // Return without effects + return + + // Run normally + . = ..() +*/ + /datum/species/handle_body(mob/living/carbon/human/species_human) . = ..() diff --git a/modular_zzplurt/code/modules/mob/living/carbon/human/species_types/hemophage_species.dm b/modular_zzplurt/code/modules/mob/living/carbon/human/species_types/hemophage_species.dm new file mode 100644 index 0000000000000..e102d12c0dea1 --- /dev/null +++ b/modular_zzplurt/code/modules/mob/living/carbon/human/species_types/hemophage_species.dm @@ -0,0 +1,32 @@ +/datum/species/hemophage/New() + // Remove traits + inherent_traits -= list( + TRAIT_NOBREATH, + TRAIT_OXYIMMUNE, + TRAIT_VIRUSIMMUNE, + ) + + // Restore lungs + mutantlungs = /datum/species::mutantlungs + + // Disable veteran restriction + veteran_only = FALSE + + // Return original + . = ..() + +// Called when a mob gains this species +/datum/species/hemophage/on_species_gain(mob/living/carbon/human/human_target, datum/species/old_species, pref_load) + . = ..() + + // Add profane penalties + human_target.AddElementTrait(TRAIT_CHAPEL_WEAKNESS, SPECIES_HEMOPHAGE, /datum/element/chapel_weakness) + human_target.AddElementTrait(TRAIT_HOLYWATER_WEAKNESS, SPECIES_HEMOPHAGE, /datum/element/holywater_weakness) + +// Called when a mob loses this species +/datum/species/hemophage/on_species_loss(mob/living/carbon/human/human_target, datum/species/new_species, pref_load) + . = ..() + + // Remove profane penalties + REMOVE_TRAIT(human_target, TRAIT_CHAPEL_WEAKNESS, SPECIES_HEMOPHAGE) + REMOVE_TRAIT(human_target, TRAIT_HOLYWATER_WEAKNESS, SPECIES_HEMOPHAGE) diff --git a/modular_zzplurt/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/modular_zzplurt/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm new file mode 100644 index 0000000000000..e467f4ef7295b --- /dev/null +++ b/modular_zzplurt/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -0,0 +1,12 @@ +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/GiveTarget(new_target) + var/targets_the_same = (new_target == target) + . = ..() + + // Escape clause from original + if(!(. && target && !targets_the_same)) + return + + // Unique dialog for Cursed Blood + if(HAS_TRAIT(target, TRAIT_CURSED_BLOOD)) + say(pick("Hunter, you must accept your death, be freed from the night.","The night, and the dream, were long...","Beasts all over the shop... You'll be one of them, sooner or later...","The night blocks all sight...")) + diff --git a/modular_zzplurt/code/modules/modular_implants/code/nif_implants.dm b/modular_zzplurt/code/modules/modular_implants/code/nif_implants.dm new file mode 100644 index 0000000000000..6a6853cd67af6 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nif_implants.dm @@ -0,0 +1,8 @@ +// Increase maximum NIFsofts +// Compensates for quirks being converted to NIFs + +/obj/item/organ/internal/cyberimp/brain/nif + max_nifsofts = 10 + +/obj/item/organ/internal/cyberimp/brain/nif/roleplay_model + max_nifsofts = 6 diff --git a/modular_zzplurt/code/modules/modular_implants/code/nif_research.dm b/modular_zzplurt/code/modules/modular_implants/code/nif_research.dm new file mode 100644 index 0000000000000..8b9ae38e94a86 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nif_research.dm @@ -0,0 +1,35 @@ +// Add NIFs to alien surgery node +/datum/techweb_node/alien_surgery/New() + design_ids += list( + "nifsoft_hypno_brainwash", + ) + return ..() + +// Design for brainwash NIFSoft +/datum/design/nifsoft_hud/nifsoft_hypno_brainwash + name = "Mesmer Eye NIFSoft" + desc = "A NIFSoft datadisk containing the experimental Mesmer Eye NIFsoft." + id = "nifsoft_hypno_brainwash" + build_path = /obj/item/disk/nifsoft_uploader/dorms/hypnosis/brainwashing + //departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +// Design for storage concealment NIFSoft +/datum/design/nifsoft_hud/nifsoft_storage_concealment + name = "Storage Concealment NIFSoft" + desc = "A NIFSoft datadisk containing the Storage Concealment NIFsoft." + id = "nifsoft_storage_concealment" + build_path = /obj/item/disk/nifsoft_uploader/nif_hide_backpack_disk + +// Design for rapid disrobe NIFSoft +/datum/design/nifsoft_hud/nifsoft_rapid_disrobe + name = "Emergency Clothing Disruption NIFSoft" + desc = "A NIFSoft datadisk containing the Emergency Clothing Disruption NIFsoft." + id = "nifsoft_rapid_disrobe" + build_path = /obj/item/disk/nifsoft_uploader/dorms/nif_disrobe_disk + +// Design for genital fluid NIFSoft +/datum/design/nifsoft_hud/nifsoft_gfluid + name = "Genital Fluid Inducer NIFSoft" + desc = "A NIFSoft datadisk containing the Genital Fluid Inducer NIFsoft." + id = "nifsoft_gfluid" + build_path = /obj/item/disk/nifsoft_uploader/dorms/nif_gfluid_disk diff --git a/modular_zzplurt/code/modules/modular_implants/code/nifsofts/base_types/action_granter.dm b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/base_types/action_granter.dm new file mode 100644 index 0000000000000..1987fd6af304b --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/base_types/action_granter.dm @@ -0,0 +1,18 @@ +// Variant with pre-configured settings +// This is designed to replace old 'free' quirks +/datum/nifsoft/action_granter/free + // Very low purchase price + purchase_price = 100 + + // Disable energy cost + activation_cost = 0 + active_cost = 0 + + // Allow persistence + able_to_keep = TRUE + + // Default to persistent + keep_installed = TRUE + + // Do not grant reward points + rewards_points_rate = 0 diff --git a/modular_zzplurt/code/modules/modular_implants/code/nifsofts/brainwashing.dm b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/brainwashing.dm new file mode 100644 index 0000000000000..e470814349737 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/brainwashing.dm @@ -0,0 +1,19 @@ +// Advanced brainwash disk +/obj/item/disk/nifsoft_uploader/dorms/hypnosis/brainwashing + name = "mesmer eye" + loaded_nifsoft = /datum/nifsoft/action_granter/hypnosis/brainwashing + +// More advanced variant for full brainwashing +/datum/nifsoft/action_granter/hypnosis/brainwashing + name = "Mesmer Eye" + program_desc = "Based on illegal abductor technology, the Mesmer Eye NIFSoft allows the user to completely control others actions. Unlike Libidine Eye, victims are unable to resist once given an order. You will be held responsible for your target's actions." + + // Has a cost + active_cost = 0.1 + activation_cost = 1 + + // Cannot be kept + able_to_keep = FALSE + + // Grants different action + action_to_grant = /datum/action/cooldown/hypnotize/brainwash diff --git a/modular_zzplurt/code/modules/modular_implants/code/nifsofts/gfluid.dm b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/gfluid.dm new file mode 100644 index 0000000000000..9a7168565d7a7 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/gfluid.dm @@ -0,0 +1,12 @@ +/obj/item/disk/nifsoft_uploader/dorms/nif_gfluid_disk + name = "genital fluid" + loaded_nifsoft = /datum/nifsoft/action_granter/free/nif_gfluid + +// Currently doesn't work because fluids aren't implemented +/datum/nifsoft/action_granter/free/nif_gfluid + name = "Genital Fluid Inducer" + program_desc = "Allows the user to induce their genitals into producing a specific reagent. Will prevent harmful liquids from being accepted as a genital fluid replacement." + buying_category = NIFSOFT_CATEGORY_FUN + lewd_nifsoft = TRUE + ui_icon = "eye" + //action_to_grant = /datum/action/item_action/genital_fluid_infuse // Not implemented yet diff --git a/modular_zzplurt/code/modules/modular_implants/code/nifsofts/hypnosis.dm b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/hypnosis.dm new file mode 100644 index 0000000000000..9bff7d65eae25 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/hypnosis.dm @@ -0,0 +1,10 @@ +/datum/nifsoft/action_granter/hypnosis + // Keep this between shifts + able_to_keep = TRUE + + // Disable use cost + active_cost = 0 + activation_cost = 0 + + // Replace action + action_to_grant = /datum/action/cooldown/hypnotize diff --git a/modular_zzplurt/code/modules/modular_implants/code/nifsofts/rapid_disrobe.dm b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/rapid_disrobe.dm new file mode 100644 index 0000000000000..2acb01def17db --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/rapid_disrobe.dm @@ -0,0 +1,32 @@ +/obj/item/disk/nifsoft_uploader/dorms/nif_disrobe_disk + name = "rapid disrobe" + loaded_nifsoft = /datum/nifsoft/action_granter/free/nif_disrobe + +/datum/nifsoft/action_granter/free/nif_disrobe + name = "Emergency Clothing Disruption Field" + program_desc = "Generates a lining of nanites along the epidermis with high power static charge emitters, allowing for the rapid removal of clothing. Precision not guaranteed." + buying_category = NIFSOFT_CATEGORY_FUN + lewd_nifsoft = TRUE + ui_icon = "eye" + action_to_grant = /datum/action/innate/nif_disrobe_action + +/datum/action/innate/nif_disrobe_action + name = "Rapid Disrobe" + desc = "Instantly eject all covering clothing from your body." + background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' + background_icon_state = "android" + // PLACEHOLDER ICON - PLEASE REPLACE + button_icon = 'modular_zzplurt/icons/mob/actions/misc_actions.dmi' + button_icon_state = "no_uniform" + +// Runs on toggling the implant +/datum/action/innate/nif_disrobe_action/Activate() + // Define target + var/mob/living/carbon/target_user = owner + + // Check if target exists + if(!istype(target_user)) + return FALSE + + // Perform LPD effect + target_user.clothing_burst(TRUE) diff --git a/modular_zzplurt/code/modules/modular_implants/code/nifsofts/shapeshifter.dm b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/shapeshifter.dm new file mode 100644 index 0000000000000..a5de7edac9d00 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/shapeshifter.dm @@ -0,0 +1,10 @@ +/datum/nifsoft/action_granter/shapeshifter + // Keep this between shifts + able_to_keep = TRUE + + // Disable use cost + active_cost = 0 + activation_cost = 0 + + // Allow on all NIF units + compatible_nifs = list(/obj/item/organ/internal/cyberimp/brain/nif) diff --git a/modular_zzplurt/code/modules/modular_implants/code/nifsofts/storage_concealment.dm b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/storage_concealment.dm new file mode 100644 index 0000000000000..2b391b367627b --- /dev/null +++ b/modular_zzplurt/code/modules/modular_implants/code/nifsofts/storage_concealment.dm @@ -0,0 +1,16 @@ +/obj/item/disk/nifsoft_uploader/nif_hide_backpack_disk + name = "storage concealment" + loaded_nifsoft = /datum/nifsoft/action_granter/free/nif_hide_backpack + +/datum/nifsoft/action_granter/free/nif_hide_backpack + name = "Chameleon Storage Concealment" + program_desc = "Emits a weak psychic signal that conceals the presence of back-equipped gear items." + buying_category = NIFSOFT_CATEGORY_COSMETIC + ui_icon = "paintbrush" + action_to_grant = /datum/action/item_action/hide_backpack/nifsoft + +// Variant to change icons +// Otherwise identical to old implant method +/datum/action/item_action/hide_backpack/nifsoft + background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' + background_icon_state = "android" diff --git a/modular_zzplurt/code/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/breast_milk.dm b/modular_zzplurt/code/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/breast_milk.dm new file mode 100644 index 0000000000000..5f07f18cda625 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/breast_milk.dm @@ -0,0 +1,9 @@ +// New implementation +/datum/reagent/consumable/breast_milk + // Updated description + description = "An opaque white liquid taken fresh from the source." + + // Copy from normal milk + color = /datum/reagent/consumable/milk::color + ph = /datum/reagent/consumable/milk::ph + chemical_flags = /datum/reagent/consumable/milk::chemical_flags diff --git a/modular_zzplurt/code/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/cum.dm b/modular_zzplurt/code/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/cum.dm new file mode 100644 index 0000000000000..9d99e190c1cc3 --- /dev/null +++ b/modular_zzplurt/code/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/cum.dm @@ -0,0 +1,25 @@ +// Default type cum +/datum/reagent/consumable/cum/on_mob_add(mob/living/affected_mob, amount) + . = ..() + + /* + // Send signals for adding reagent + SEND_SIGNAL(affected_mob, COMSIG_QUIRK_D4C_CONSUME) + SEND_SIGNAL(affected_mob, COMSIG_QUIRK_CONCUBUS_CONSUME, src, amount) + */ + + // Send signals for adding reagent + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_ADD_CUM, src, amount) + +// Female type cum +/datum/reagent/consumable/femcum/on_mob_add(mob/living/affected_mob, amount) + . = ..() + + /* + // Send signals for adding reagent + SEND_SIGNAL(affected_mob, COMSIG_QUIRK_D4C_CONSUME) + SEND_SIGNAL(affected_mob, COMSIG_QUIRK_CONCUBUS_CONSUME, src, amount) + */ + + // Send signals for adding reagent + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_ADD_CUM, src, amount) diff --git a/modular_zzplurt/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/modular_zzplurt/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm new file mode 100644 index 0000000000000..42dfd285d6014 --- /dev/null +++ b/modular_zzplurt/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm @@ -0,0 +1,13 @@ +// Reagent add: Breast Milk +/datum/reagent/consumable/breast_milk/on_mob_add(mob/living/affected_mob, amount) + . = ..() + + // Send signal for adding reagent + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_ADD_BREASTMILK, src, amount) + +// Reagent metabolize: Nuka Cola +/datum/reagent/consumable/nuka_cola/on_mob_metabolize(mob/living/affected_mob) + . = ..() + + // Send signal for adding reagent + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_METABOLIZE_NUKACOLA) diff --git a/modular_zzplurt/code/modules/reagents/chemistry/reagents/food_reagents.dm b/modular_zzplurt/code/modules/reagents/chemistry/reagents/food_reagents.dm new file mode 100644 index 0000000000000..beeaa008849ec --- /dev/null +++ b/modular_zzplurt/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -0,0 +1,11 @@ +// Reagent process: Salt +/datum/reagent/consumable/salt/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_PROCESS_SALT, src, seconds_per_tick, times_fired) + +// Reagent expose: Salt +/datum/reagent/consumable/salt/expose_mob(mob/living/affected_mob, methods, reac_volume) + . = ..() + + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_EXPOSE_SALT, src, methods, reac_volume) diff --git a/modular_zzplurt/code/modules/reagents/chemistry/reagents/other_reagents.dm b/modular_zzplurt/code/modules/reagents/chemistry/reagents/other_reagents.dm new file mode 100644 index 0000000000000..ef0eb4fe8d555 --- /dev/null +++ b/modular_zzplurt/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -0,0 +1,68 @@ +/** + * This is a special reagent used by 'alternative food' quirks + * It functionally matches Nutriment, but can be processed with liverless metabolism + * It should not be used for any other purpose outside quirks + */ +/datum/reagent/consumable/notriment + name = "Strange Nutriment" + description = "An exotic form of nutriment produced by unusual digestive systems." + reagent_state = /datum/reagent/consumable/nutriment::reagent_state + nutriment_factor = /datum/reagent/consumable/nutriment::nutriment_factor + color = /datum/reagent/consumable/nutriment::color + // Allow processing without a liver + self_consuming = TRUE + +// Reagent process: Hell Water +/datum/reagent/hellwater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + // Check for Cursed Blood + if(HAS_TRAIT(affected_mob, TRAIT_CURSED_BLOOD)) + // Send signal for processing reagent + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_PROCESS_HELLWATER, src, seconds_per_tick, times_fired) + + // Block other effects + return + + // Run normally + . = ..() + +// Reagent metabolize: Holy Water +/datum/reagent/water/holywater/on_mob_metabolize(mob/living/affected_mob) + . = ..() + + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_METABOLIZE_HOLYWATER) + +// Reagent end metabolize: Holy Water +/datum/reagent/water/holywater/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_METABOLIZE_END_HOLYWATER) + +// Reagent process: Holy Water +/datum/reagent/water/holywater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_PROCESS_HOLYWATER, src, seconds_per_tick, times_fired) + +// Reagent expose: Holy Water +/datum/reagent/water/holywater/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=0) + . = ..() + + SEND_SIGNAL(exposed_mob, COMSIG_REAGENT_EXPOSE_HOLYWATER, src, methods, reac_volume, show_message, touch_protection) + +// Reagent Add: Blood +/datum/reagent/blood/on_mob_add(mob/living/affected_mob, amount) + . = ..() + + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_ADD_BLOOD, src, amount, data) + +// Reagent process: Salt Water +/datum/reagent/water/salt/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + + SEND_SIGNAL(affected_mob, COMSIG_REAGENT_PROCESS_SALT, src, seconds_per_tick, times_fired) + +// Reagent expose: Salt Water +/datum/reagent/water/salt/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=0) + . = ..() + + SEND_SIGNAL(exposed_mob, COMSIG_REAGENT_EXPOSE_SALT, src, methods, reac_volume, show_message, touch_protection) diff --git a/modular_zzplurt/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/modular_zzplurt/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm new file mode 100644 index 0000000000000..cdc7a9ecd0718 --- /dev/null +++ b/modular_zzplurt/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm @@ -0,0 +1,7 @@ +/obj/item/organ/internal/cyberimp/chest/nutriment/on_life() + // Check if this user can process nutriment + if(HAS_TRAIT(owner, TRAIT_LIVERLESS_METABOLISM)) + return + + // Continue normally + . = ..() diff --git a/modular_zzplurt/code/modules/vending/lustwish.dm b/modular_zzplurt/code/modules/vending/lustwish.dm new file mode 100644 index 0000000000000..c3b6daa331faa --- /dev/null +++ b/modular_zzplurt/code/modules/vending/lustwish.dm @@ -0,0 +1,20 @@ +#define STOCK_NIFSOFT 6 + +// Based on Skyrat's vending overrides +/obj/machinery/vending/dorms + // New premium items + zzplurt_premium = list( + // Original software + // This is in the PDA + /* + /obj/item/disk/nifsoft_uploader/dorms/hypnosis = STOCK_NIFSOFT, + /obj/item/disk/nifsoft_uploader/shapeshifter = STOCK_NIFSOFT, + */ + + // New software + /obj/item/disk/nifsoft_uploader/dorms/nif_disrobe_disk = STOCK_NIFSOFT, + /obj/item/disk/nifsoft_uploader/nif_hide_backpack_disk = STOCK_NIFSOFT, + /obj/item/disk/nifsoft_uploader/dorms/nif_gfluid_disk = STOCK_NIFSOFT, + ) + +#undef STOCK_NIFSOFT diff --git a/modular_zzplurt/code/modules/vending/vending.dm b/modular_zzplurt/code/modules/vending/vending.dm new file mode 100644 index 0000000000000..226e027b33c13 --- /dev/null +++ b/modular_zzplurt/code/modules/vending/vending.dm @@ -0,0 +1,46 @@ +// Taken from Modular Vending in Bubber +// All entries will become null after Initialize, to free up memory. + +/obj/machinery/vending + // Additions to the `products` list + var/list/zzplurt_products + // Additions to the `product_categories` list + var/list/zzplurt_product_categories + // Additions to the `premium` list + var/list/zzplurt_premium + // Additions to the `contraband` list + var/list/zzplurt_contraband + +/obj/machinery/vending/Initialize(mapload) + if(zzplurt_products) + // We need this, because duplicates screw up the spritesheet! + for(var/item_to_add in zzplurt_products) + products[item_to_add] = zzplurt_products[item_to_add] + + if(zzplurt_product_categories) + for(var/category in zzplurt_product_categories) + var/already_exists = FALSE + for(var/existing_category in product_categories) + if(existing_category["name"] == category["name"]) + existing_category["products"] += category["products"] + already_exists = TRUE + break + + if(!already_exists) + product_categories += category + + if(zzplurt_premium) + // We need this, because duplicates screw up the spritesheet! + for(var/item_to_add in zzplurt_premium) + premium[item_to_add] = zzplurt_premium[item_to_add] + + if(zzplurt_contraband) + // We need this, because duplicates screw up the spritesheet! + for(var/item_to_add in zzplurt_contraband) + contraband[item_to_add] = zzplurt_contraband[item_to_add] + + QDEL_NULL(zzplurt_products) + QDEL_NULL(zzplurt_product_categories) + QDEL_NULL(zzplurt_premium) + QDEL_NULL(zzplurt_contraband) + return ..() diff --git a/modular_zzplurt/icons/mob/actions/actions_flightsuit.dmi b/modular_zzplurt/icons/mob/actions/actions_flightsuit.dmi new file mode 100644 index 0000000000000..3121c243555fc Binary files /dev/null and b/modular_zzplurt/icons/mob/actions/actions_flightsuit.dmi differ diff --git a/modular_zzplurt/icons/mob/actions/lewd_actions/lewd_icons.dmi b/modular_zzplurt/icons/mob/actions/lewd_actions/lewd_icons.dmi new file mode 100644 index 0000000000000..a9f7d0f588611 Binary files /dev/null and b/modular_zzplurt/icons/mob/actions/lewd_actions/lewd_icons.dmi differ diff --git a/modular_zzplurt/icons/mob/actions/misc_actions.dmi b/modular_zzplurt/icons/mob/actions/misc_actions.dmi new file mode 100644 index 0000000000000..dc254301e736d Binary files /dev/null and b/modular_zzplurt/icons/mob/actions/misc_actions.dmi differ diff --git a/modular_zzplurt/icons/obj/art/statue.dmi b/modular_zzplurt/icons/obj/art/statue.dmi new file mode 100644 index 0000000000000..358c6c21edf9b Binary files /dev/null and b/modular_zzplurt/icons/obj/art/statue.dmi differ diff --git a/modular_zzplurt/icons/obj/card.dmi b/modular_zzplurt/icons/obj/card.dmi new file mode 100644 index 0000000000000..d3432696e42e0 Binary files /dev/null and b/modular_zzplurt/icons/obj/card.dmi differ diff --git a/modular_zzplurt/icons/obj/implants.dmi b/modular_zzplurt/icons/obj/implants.dmi new file mode 100644 index 0000000000000..9f1fa7dd04c0f Binary files /dev/null and b/modular_zzplurt/icons/obj/implants.dmi differ diff --git a/modular_zzplurt/icons/obj/storage.dmi b/modular_zzplurt/icons/obj/storage.dmi new file mode 100644 index 0000000000000..49377b6b9eafd Binary files /dev/null and b/modular_zzplurt/icons/obj/storage.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 115e832788582..f641cf622df0c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -521,11 +521,14 @@ #include "code\__DEFINES\~~~splurt_defines\keybinding.dm" #include "code\__DEFINES\~~~splurt_defines\mobs.dm" #include "code\__DEFINES\~~~splurt_defines\sizecode.dm" +#include "code\__DEFINES\~~~splurt_defines\spans.dm" #include "code\__DEFINES\~~~splurt_defines\species_clothing_paths.dm" #include "code\__DEFINES\~~~splurt_defines\strippable.dm" #include "code\__DEFINES\~~~splurt_defines\underwear.dm" #include "code\__DEFINES\~~~splurt_defines\dcs\signals.dm" +#include "code\__DEFINES\~~~splurt_defines\dcs\signals_reagents.dm" #include "code\__DEFINES\~~~splurt_defines\traits\declarations.dm" +#include "code\__DEFINES\~~~splurt_defines\traits\defines.dm" #include "code\__HELPERS\_auxtools_api.dm" #include "code\__HELPERS\_dreamluau.dm" #include "code\__HELPERS\_lists.dm" @@ -645,6 +648,7 @@ #include "code\__HELPERS\~skyrat_helpers\level_traits.dm" #include "code\__HELPERS\~skyrat_helpers\logging.dm" #include "code\__HELPERS\~skyrat_helpers\unsorted.dm" +#include "code\__HELPERS\~splurt_helpers\is_helpers.dm" #include "code\__HELPERS\~splurt_helpers\mob.dm" #include "code\_globalvars\_regexes.dm" #include "code\_globalvars\admin.dm" @@ -9402,6 +9406,7 @@ #include "modular_zubbers\master_files\skyrat\modules\verbs\code\subtle.dm" #include "modular_zzplurt\code\_globalvars\mobs.dm" #include "modular_zzplurt\code\_globalvars\lists\global_lewd.dm" +#include "modular_zzplurt\code\_onclick\observer.dm" #include "modular_zzplurt\code\_onclick\hud\hud.dm" #include "modular_zzplurt\code\_onclick\hud\human.dm" #include "modular_zzplurt\code\controllers\configuration\entries\discord.dm" @@ -9411,16 +9416,71 @@ #include "modular_zzplurt\code\datums\outfit.dm" #include "modular_zzplurt\code\datums\sprite_accessories.dm" #include "modular_zzplurt\code\datums\components\crafting\crafting.dm" +#include "modular_zzplurt\code\datums\components\crafting\equipment.dm" +#include "modular_zzplurt\code\datums\elements\chapel_weakness.dm" +#include "modular_zzplurt\code\datums\elements\flutter_move.dm" +#include "modular_zzplurt\code\datums\elements\headpat.dm" #include "modular_zzplurt\code\datums\elements\holder_micro.dm" +#include "modular_zzplurt\code\datums\elements\holywater_weakness.dm" #include "modular_zzplurt\code\datums\elements\mob_holder.dm" #include "modular_zzplurt\code\datums\elements\skirt_peeking.dm" +#include "modular_zzplurt\code\datums\id_trim\quirks.dm" #include "modular_zzplurt\code\datums\keybinding\human.dm" #include "modular_zzplurt\code\datums\keybinding\living.dm" #include "modular_zzplurt\code\datums\mutations\body.dm" +#include "modular_zzplurt\code\datums\quirks\negative_quirks\bad_touch.dm" +#include "modular_zzplurt\code\datums\quirks\negative_quirks\dumb_for_cum.dm" +#include "modular_zzplurt\code\datums\quirks\negative_quirks\flimsy.dm" +#include "modular_zzplurt\code\datums\quirks\negative_quirks\gifted.dm" +#include "modular_zzplurt\code\datums\quirks\negative_quirks\obese.dm" +#include "modular_zzplurt\code\datums\quirks\negative_quirks\overweight.dm" +#include "modular_zzplurt\code\datums\quirks\negative_quirks\thirsty.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\body_morpher.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\choke_slut.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\concubus.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\cosglow.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\cum_plus.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\cum_sniff.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\cursed_blood.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\excitable.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\gargoyle.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\headpat_slut.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\hypnotic_gaze.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\jiggly_ass.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\kiss_slut.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\masked_mook.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\messy.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\modularlimbs.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\nudist.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\personalspace.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\phobia.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\sodiumsensetivity.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\storage_concealment.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\undead.dm" +#include "modular_zzplurt\code\datums\quirks\neutral_quirks\werewolf.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\arachnid.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\ash_resistance.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\bloodfledge.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\breathless.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\cloth_eater.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\flutter.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\hallowed.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\hard_soles.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\radfiend.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\restorative_metabolism.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\tough.dm" +#include "modular_zzplurt\code\datums\quirks\positive_quirks\vacuum_resistance.dm" +#include "modular_zzplurt\code\datums\status_effects\quirk_examine.dm" #include "modular_zzplurt\code\datums\traits\neutral\oversized.dm" +#include "modular_zzplurt\code\datums\weather\weather_types\ash_storm.dm" #include "modular_zzplurt\code\game\objects\items.dm" +#include "modular_zzplurt\code\game\objects\items\cards_ids.dm" +#include "modular_zzplurt\code\game\objects\items\emags.dm" #include "modular_zzplurt\code\game\objects\items\holy_weapons.dm" #include "modular_zzplurt\code\game\objects\items\devices\transfer_valve.dm" +#include "modular_zzplurt\code\game\objects\items\implants\implant_disrobe.dm" +#include "modular_zzplurt\code\game\objects\items\implants\implant_gfluid.dm" +#include "modular_zzplurt\code\game\objects\items\implants\implant_hide_backpack.dm" #include "modular_zzplurt\code\game\objects\items\lewd_items\size_items.dm" #include "modular_zzplurt\code\modules\admin\player_panel.dm" #include "modular_zzplurt\code\modules\admin\playtimes.dm" @@ -9439,6 +9499,7 @@ #include "modular_zzplurt\code\modules\client\preferences\player_panel.dm" #include "modular_zzplurt\code\modules\client\verbs\looc.dm" #include "modular_zzplurt\code\modules\client\verbs\ooc.dm" +#include "modular_zzplurt\code\modules\clothing\masks\gasmask.dm" #include "modular_zzplurt\code\modules\clothing\underwear\_underwear.dm" #include "modular_zzplurt\code\modules\clothing\underwear\briefs.dm" #include "modular_zzplurt\code\modules\clothing\underwear\shirt.dm" @@ -9449,6 +9510,7 @@ #include "modular_zzplurt\code\modules\clothing\underwear\~generated_files\shirt_edits.dm" #include "modular_zzplurt\code\modules\clothing\underwear\~generated_files\socks.dm" #include "modular_zzplurt\code\modules\clothing\underwear\~generated_files\socks_edits.dm" +#include "modular_zzplurt\code\modules\customization\modules\mob\living\carbon\human\species\hemophage\corrupted_tongue.dm" #include "modular_zzplurt\code\modules\discord\tgs_commands.dm" #include "modular_zzplurt\code\modules\discord\verbs.dm" #include "modular_zzplurt\code\modules\hud\_hud.dm" @@ -9464,6 +9526,8 @@ #include "modular_zzplurt\code\modules\mob\living\living_defines.dm" #include "modular_zzplurt\code\modules\mob\living\living_update_icons.dm" #include "modular_zzplurt\code\modules\mob\living\basic\space_fauna\wumborian_fugu\fugu_gland.dm" +#include "modular_zzplurt\code\modules\mob\living\carbon\carbon.dm" +#include "modular_zzplurt\code\modules\mob\living\carbon\carbon_defense.dm" #include "modular_zzplurt\code\modules\mob\living\carbon\examine.dm" #include "modular_zzplurt\code\modules\mob\living\carbon\human\_species.dm" #include "modular_zzplurt\code\modules\mob\living\carbon\human\human.dm" @@ -9474,10 +9538,26 @@ #include "modular_zzplurt\code\modules\mob\living\carbon\human\inventory.dm" #include "modular_zzplurt\code\modules\mob\living\carbon\human\life.dm" #include "modular_zzplurt\code\modules\mob\living\carbon\human\species.dm" +#include "modular_zzplurt\code\modules\mob\living\carbon\human\species_types\hemophage_species.dm" +#include "modular_zzplurt\code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nif_implants.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nif_research.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nifsofts\brainwashing.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nifsofts\gfluid.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nifsofts\hypnosis.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nifsofts\rapid_disrobe.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nifsofts\shapeshifter.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nifsofts\storage_concealment.dm" +#include "modular_zzplurt\code\modules\modular_implants\code\nifsofts\base_types\action_granter.dm" +#include "modular_zzplurt\code\modules\modular_items\lewd_items\code\lewd_chemistry\reagents\breast_milk.dm" +#include "modular_zzplurt\code\modules\modular_items\lewd_items\code\lewd_chemistry\reagents\cum.dm" #include "modular_zzplurt\code\modules\movespeed\modifiers\components.dm" #include "modular_zzplurt\code\modules\movespeed\modifiers\innate.dm" #include "modular_zzplurt\code\modules\reagents\chemistry\reagents.dm" #include "modular_zzplurt\code\modules\reagents\chemistry\machinery\chem_dispenser.dm" +#include "modular_zzplurt\code\modules\reagents\chemistry\reagents\food_reagents.dm" +#include "modular_zzplurt\code\modules\reagents\chemistry\reagents\other_reagents.dm" +#include "modular_zzplurt\code\modules\reagents\chemistry\reagents\drinks\drink_reagents.dm" #include "modular_zzplurt\code\modules\reagents\reagent_containers\cups\sizeitems.dm" #include "modular_zzplurt\code\modules\resize\resizing.dm" #include "modular_zzplurt\code\modules\resize\sizechems.dm" @@ -9496,8 +9576,11 @@ #include "modular_zzplurt\code\modules\surgery\organs\genitals\belly.dm" #include "modular_zzplurt\code\modules\surgery\organs\genitals\butt.dm" #include "modular_zzplurt\code\modules\surgery\organs\genitals\genitals.dm" +#include "modular_zzplurt\code\modules\surgery\organs\internal\cyberimp\augments_chest.dm" #include "modular_zzplurt\code\modules\surgery\organs\internal\eyes\_eyes.dm" #include "modular_zzplurt\code\modules\surgery\organs\internal\tongue\_tongue.dm" +#include "modular_zzplurt\code\modules\vending\lustwish.dm" +#include "modular_zzplurt\code\modules\vending\vending.dm" #include "modular_zzplurt\code\modules\wiremod\core\integrated_circuit.dm" // END_INCLUDE