diff --git a/code/__DEFINES/food.dm b/code/__DEFINES/food.dm index b7e5b38482d16..bb604fcdc70c5 100644 --- a/code/__DEFINES/food.dm +++ b/code/__DEFINES/food.dm @@ -19,6 +19,7 @@ #define BUGS (1<<18) #define GORE (1<<19) #define STONE (1<<20) +#define BLOODY (1<<21) // DOPPLER ADDITION - Hemophage Food DEFINE_BITFIELD(foodtypes, list( "MEAT" = MEAT, @@ -42,6 +43,7 @@ DEFINE_BITFIELD(foodtypes, list( "BUGS" = BUGS, "GORE" = GORE, "STONE" = STONE, + "BLOODY" = BLOODY, //Doppler Edit Addition )) /// A list of food type names, in order of their flags @@ -67,6 +69,7 @@ DEFINE_BITFIELD(foodtypes, list( "BUGS", \ "GORE", \ "STONE", \ + "BLOODY", \ ) /// IC meaning (more or less) for food flags @@ -92,6 +95,7 @@ DEFINE_BITFIELD(foodtypes, list( "Bugs", \ "Gore", \ "Rocks", \ + "Bloody", \ ) #define DRINK_REVOLTING 1 @@ -101,6 +105,7 @@ DEFINE_BITFIELD(foodtypes, list( #define DRINK_FANTASTIC 5 #define FOOD_AMAZING 6 +#define RACE_DRINK 7 // DOPPLER EDIT ADDITION #define FOOD_QUALITY_NORMAL 1 #define FOOD_QUALITY_NICE 2 diff --git a/code/__DEFINES/~doppler_defines/construction.dm b/code/__DEFINES/~doppler_defines/construction.dm new file mode 100644 index 0000000000000..10efde968debb --- /dev/null +++ b/code/__DEFINES/~doppler_defines/construction.dm @@ -0,0 +1 @@ +#define CAT_HEMOPHAGE "Hemophage Food" diff --git a/code/__DEFINES/~doppler_defines/declarations.dm b/code/__DEFINES/~doppler_defines/declarations.dm index 1461f8cae6e67..bc4ec3858dbf7 100644 --- a/code/__DEFINES/~doppler_defines/declarations.dm +++ b/code/__DEFINES/~doppler_defines/declarations.dm @@ -4,3 +4,5 @@ #define TRAIT_DETECTIVE "detective_ability" /// Trait for the excitable quirk, woof! #define TRAIT_EXCITABLE "wagwag" +/// Trait for hemophages particularly! +#define TRAIT_OXYIMMUNE "oxyimmune" // Immune to oxygen damage, ideally give this to all non-breathing species or bad stuff will happen diff --git a/code/__DEFINES/~doppler_defines/is_helpers.dm b/code/__DEFINES/~doppler_defines/is_helpers.dm index 0b00d1d1b7187..888703680c904 100644 --- a/code/__DEFINES/~doppler_defines/is_helpers.dm +++ b/code/__DEFINES/~doppler_defines/is_helpers.dm @@ -3,5 +3,6 @@ //Customization bases #define isinsectoid(A) (is_species(A, /datum/species/insectoid)) #define issnail(A) (is_species(A, /datum/species/snail)) +#define ishemophage(A) (is_species(A, /datum/species/hemophage)) //Species with green blood #define hasgreenblood(A) (isinsectoid(A) || HAS_TRAIT(A, TRAIT_GREEN_BLOOD)) diff --git a/code/__DEFINES/~doppler_defines/reagents.dm b/code/__DEFINES/~doppler_defines/reagents.dm new file mode 100644 index 0000000000000..981a465137ccb --- /dev/null +++ b/code/__DEFINES/~doppler_defines/reagents.dm @@ -0,0 +1,2 @@ +/// This reagent is useful for blood regeneration. Useful for Hemophages. +#define REAGENT_BLOOD_REGENERATING (1<<0) diff --git a/code/__DEFINES/~doppler_defines/signals.dm b/code/__DEFINES/~doppler_defines/signals.dm index 2cb1bc8d372ef..7c267ce6e0ea1 100644 --- a/code/__DEFINES/~doppler_defines/signals.dm +++ b/code/__DEFINES/~doppler_defines/signals.dm @@ -8,3 +8,10 @@ #define COMPONENT_POWER_SUCCESS (1<<0) #define COMPONENT_NO_CELL (1<<1) #define COMPONENT_NO_CHARGE (1<<2) + +/// For when a Hemophage's pulsating tumor gets added to their body. +#define COMSIG_PULSATING_TUMOR_ADDED "pulsating_tumor_added" +/// For when a Hemophage's pulsating tumor gets removed from their body. +#define COMSIG_PULSATING_TUMOR_REMOVED "pulsating_tumor_removed" +/// From /obj/item/organ/internal/stomach/after_eat(atom/edible) +#define COMSIG_STOMACH_AFTER_EAT "stomach_after_eat" diff --git a/code/__DEFINES/~doppler_defines/species.dm b/code/__DEFINES/~doppler_defines/species.dm index fa9a89767e2e8..a3ed2dd062f18 100644 --- a/code/__DEFINES/~doppler_defines/species.dm +++ b/code/__DEFINES/~doppler_defines/species.dm @@ -4,3 +4,4 @@ #define SPECIES_ANTHROMORPH "anthromorph" #define SPECIES_AQUATIC "aquatic" #define SPECIES_INSECTOID "insectoid" +#define SPECIES_HEMOPHAGE "hemophage" diff --git a/code/__DEFINES/~doppler_defines/traits.dm b/code/__DEFINES/~doppler_defines/traits.dm index 315191bd4a30f..5abb9806d6d8f 100644 --- a/code/__DEFINES/~doppler_defines/traits.dm +++ b/code/__DEFINES/~doppler_defines/traits.dm @@ -15,3 +15,6 @@ // Trait that lets golems put stone limbs back on #define TRAIT_GOLEM_LIMBATTACHMENT "golem_limbattachment" + +/// Trait that was granted by a reagent. +#define REAGENT_TRAIT "reagent" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index ac5d281942aa4..6660604acd69f 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -691,6 +691,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CURRENTLY_GLASSBLOWING" = TRAIT_CURRENTLY_GLASSBLOWING, "TRAIT_ANIMALISTIC" = TRAIT_ANIMALISTIC, "TRAIT_GLASSBLOWING" = TRAIT_GLASSBLOWING, + "TRAIT_OXYIMMUNE" = TRAIT_OXYIMMUNE, //Doppler Edit Addition - Needed for hemophages. "TRAIT_XENOARCH_QUALIFIED" = TRAIT_XENOARCH_QUALIFIED, "TRAIT_DETECTIVE" = TRAIT_DETECTIVE, "TRAIT_EXCITABLE" = TRAIT_EXCITABLE, diff --git a/code/_globalvars/~doppler_globalvars/bitfields.dm b/code/_globalvars/~doppler_globalvars/bitfields.dm index 2795c9528166d..09297a1e42c0f 100644 --- a/code/_globalvars/~doppler_globalvars/bitfields.dm +++ b/code/_globalvars/~doppler_globalvars/bitfields.dm @@ -2,3 +2,7 @@ DEFINE_BITFIELD(obj_flags_doppler, list( "ANVIL_REPAIR" = ANVIL_REPAIR, )) + +DEFINE_BITFIELD(chemical_flags_doppler, list( + "REAGENT_BLOOD_REGENERATING" = REAGENT_BLOOD_REGENERATING, +)) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index c41ab8ffca521..252730d182640 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -53,6 +53,10 @@ var/obj/item/the_real_food = holder.my_atom if(isitem(the_real_food) && !is_reagent_container(the_real_food)) exposed_mob.add_mob_memory(/datum/memory/good_food, food = the_real_food) + // DOPPLER EDIT ADDITION BEGIN - Race Quality Drinks + if (RACE_DRINK) + exposed_mob.add_mood_event("quality_drink", /datum/mood_event/race_drink) + // DOPPLER EDIT ADDITION END /// Gets just how much nutrition this reagent is worth for the passed mob /datum/reagent/consumable/proc/get_nutriment_factor(mob/living/carbon/eater) diff --git a/code/modules/reagents/reagent_containers/condiment.dm b/code/modules/reagents/reagent_containers/condiment.dm index c8652afda8f0e..753a2b3b908c6 100644 --- a/code/modules/reagents/reagent_containers/condiment.dm +++ b/code/modules/reagents/reagent_containers/condiment.dm @@ -62,6 +62,7 @@ M.visible_message(span_warning("[user] fed [M] from [src]."), \ span_warning("[user] fed you from [src].")) log_combat(user, M, "fed", reagents.get_reagent_log_string()) + SEND_SIGNAL(M, COMSIG_GLASS_DRANK, src, user) // DOPPLER EDIT ADDITION - Hemophages can't casually drink what's not going to regenerate their blood reagents.trans_to(M, 10, transferred_by = user, methods = INGEST) playsound(M.loc,'sound/items/drink.ogg', rand(10,50), TRUE) return TRUE diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm index 431e1d66c69a4..c6e74f3388ab4 100644 --- a/code/modules/reagents/reagent_containers/cups/_cup.dm +++ b/code/modules/reagents/reagent_containers/cups/_cup.dm @@ -84,6 +84,7 @@ to_chat(user, span_notice("You swallow a gulp of [src].")) SEND_SIGNAL(src, COMSIG_GLASS_DRANK, target_mob, user) + SEND_SIGNAL(target_mob, COMSIG_GLASS_DRANK, src, user) // DOPPLER EDIT ADDITION - Hemophages can't casually drink what's not going to regenerate their blood var/fraction = min(gulp_size/reagents.total_volume, 1) reagents.trans_to(target_mob, gulp_size, transferred_by = user, methods = INGEST) checkLiked(fraction, target_mob) diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_hemophage.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_hemophage.png new file mode 100644 index 0000000000000..475c07f50deed Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_hemophage.png differ diff --git a/config/game_options.txt b/config/game_options.txt index fe21ece55fcb1..0328addb0a581 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -370,6 +370,7 @@ ROUNDSTART_RACES aquatic ROUNDSTART_RACES insectoid ROUNDSTART_RACES genemod ROUNDSTART_RACES primitive_genemod +ROUNDSTART_RACES hemophage ## Races that are better than humans in some ways, but worse in others ROUNDSTART_RACES ethereal diff --git a/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm b/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm index f2c5e4a8069f0..fc2d88933bda5 100644 --- a/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm +++ b/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm @@ -57,10 +57,6 @@ /turf/open/water/hot_spring/proc/hotspring_mood(mob/living/swimmer) swimmer.add_mood_event("hotspring", /datum/mood_event/hotspring/nerfed) -/datum/mood_event/hotspring/nerfed - description = span_nicegreen("The water was enjoyably warm!\n") - mood_change = 2 - // Steam particles for pairing with the hotsprings above /particles/hotspring_steam diff --git a/modular_doppler/modular_customization/organs/external/tail.dm b/modular_doppler/modular_customization/organs/external/tail.dm index 61919ffa6298c..82778fca75ea8 100644 --- a/modular_doppler/modular_customization/organs/external/tail.dm +++ b/modular_doppler/modular_customization/organs/external/tail.dm @@ -91,12 +91,6 @@ var/mob/living/carbon/human/empath = entered empath.add_mood_event("dog_wag", /datum/mood_event/dog_wag) -// The mood buff itself -/datum/mood_event/dog_wag - description = "That wagging tail's excitement is infectious!" - mood_change = 1 - timeout = 30 SECONDS - /// Fox tail // /obj/item/organ/external/tail/fox diff --git a/modular_doppler/modular_food_drinks_and_chems/chemistry_reagents.dm b/modular_doppler/modular_food_drinks_and_chems/chemistry_reagents.dm index 99b6fc2339b82..744cf7e509cd0 100644 --- a/modular_doppler/modular_food_drinks_and_chems/chemistry_reagents.dm +++ b/modular_doppler/modular_food_drinks_and_chems/chemistry_reagents.dm @@ -20,20 +20,6 @@ /datum/reagent/carbondioxide process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC -/datum/reagent/iron - chemical_flags_nova = REAGENT_BLOOD_REGENERATING - -/datum/reagent/blood - chemical_flags_nova = REAGENT_BLOOD_REGENERATING // For Hemophages to be able to drink it without any issue. - -/datum/reagent/blood/on_new(list/data) - . = ..() - - if(!src.data["blood_type"]) - src.data["blood_type"] = random_blood_type() // This is so we don't get blood without a blood type spawned from something that doesn't explicitly set the blood type. - - - /datum/reagent/stable_plasma/on_mob_life(mob/living/carbon/C) if(C.mob_biotypes & MOB_ROBOTIC) C.nutrition = min(C.nutrition + 5, NUTRITION_LEVEL_FULL-1) @@ -54,6 +40,19 @@ C.nutrition = min(C.nutrition + 5, NUTRITION_LEVEL_FULL-1) ..() */ + +/datum/reagent/iron + chemical_flags_doppler = REAGENT_BLOOD_REGENERATING + +/datum/reagent/blood + chemical_flags_doppler = REAGENT_BLOOD_REGENERATING // For Hemophages to be able to drink it without any issue. + +/datum/reagent/blood/on_new(list/data) + . = ..() + + if(!src.data["blood_type"]) + src.data["blood_type"] = random_blood_type() // This is so we don't get blood without a blood type spawned from something that doesn't explicitly set the blood type. + // Catnip /datum/reagent/pax/catnip name = "Catnip" @@ -99,3 +98,26 @@ #undef DERMAGEN_SCAR_FIX_AMOUNT */ + +/** + * Check if this holder contains a reagent with a `chemical_flags_doppler` containing this flag. + * + * Arguments: + * * chemical_flag - The bitflag to search for. + * * min_volume - Checks for having a specific amount of reagents matching that `chemical_flag` + */ +/datum/reagents/proc/has_chemical_flag_doppler(chemical_flag, min_volume = 0) + var/found_amount = 0 + var/list/cached_reagents = reagent_list + for(var/datum/reagent/holder_reagent as anything in cached_reagents) + if (holder_reagent.chemical_flags_doppler & chemical_flag) + found_amount += holder_reagent.volume + if(found_amount >= min_volume) + return TRUE + + return FALSE + +/datum/reagent + /// Modular version of `chemical_flags`, so we don't have to worry about + /// it causing conflicts in the future. + var/chemical_flags_doppler = NONE diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol reagents.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol_reagents.dm similarity index 96% rename from modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol reagents.dm rename to modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol_reagents.dm index 8e639ef8f63b5..b529340534249 100644 --- a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol reagents.dm +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol_reagents.dm @@ -4,10 +4,6 @@ /datum/reagent/consumable/ethanol/whiskey process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC //let's not force the detective to change his alcohol brand - - -/datum/reagent/consumable/ethanol/bloody_mary - chemical_flags_nova = REAGENT_BLOOD_REGENERATING */ /*SYNTHETIC DRINKS @@ -127,6 +123,9 @@ */ // Other Booze +/datum/reagent/consumable/ethanol/bloody_mary + chemical_flags_doppler = REAGENT_BLOOD_REGENERATING + /datum/reagent/consumable/ethanol/hot_toddy name = "Hot Toddy" description = "An old fashioned cocktail made of honey, rum, and tea." @@ -168,7 +167,7 @@ boozepwr = 66 quality = DRINK_FANTASTIC taste_description = "overpowering sweetness with a touch of sourness, followed by iron and the sensation of a warm summer breeze" -// chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING //component drink is demon's blood, thus this drink is made with blood so hemophages can comfortably drink it + chemical_flags_doppler = REAGENT_BLOOD_REGENERATING //component drink is demon's blood, thus this drink is made with blood so hemophages can comfortably drink it /datum/glass_style/drinking_glass/sins_delight required_drink_type = /datum/reagent/consumable/ethanol/sins_delight @@ -353,7 +352,6 @@ desc = "You'd think something so balanced would actually taste nice... you'd be dead wrong." -/* MORE RACE SPECIFIC STUFF WE DON'T HAVE SUPPORT FOR YET // RACE SPECIFIC DRINKS /datum/reagent/consumable/ethanol/coldscales @@ -376,7 +374,7 @@ else quality = DRINK_GOOD return ..() - +/* /datum/reagent/consumable/ethanol/oil_drum name = "Oil Drum" color = "#000000" //(0, 0, 0) @@ -397,14 +395,14 @@ else quality = DRINK_GOOD return ..() - +*/ /datum/reagent/consumable/ethanol/nord_king name = "Nord King" color = "#EB1010" //(235, 16, 16) description = "Strong mead mixed with more honey and ethanol. Beloved by its human patrons." boozepwr = 50 //strong! taste_description = "honey and red wine" - chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING + chemical_flags_doppler = REAGENT_BLOOD_REGENERATING /datum/glass_style/drinking_glass/nord_king required_drink_type = /datum/reagent/consumable/ethanol/nord_king @@ -414,7 +412,7 @@ desc = "A dripping keg of red mead." /datum/reagent/consumable/ethanol/nord_king/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(ishumanbasic(exposed_mob) || isdwarf(exposed_mob)) + if(HAS_TRAIT(exposed_mob, TRAIT_SETTLER)) quality = RACE_DRINK else quality = DRINK_GOOD @@ -427,7 +425,7 @@ boozepwr = 10 //weak taste_description = "iron with grapejuice" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING + chemical_flags_doppler = REAGENT_BLOOD_REGENERATING /datum/glass_style/drinking_glass/velvet_kiss required_drink_type = /datum/reagent/consumable/ethanol/velvet_kiss @@ -447,7 +445,7 @@ . = ..() if(drinker.blood_volume < BLOOD_VOLUME_NORMAL) drinker.blood_volume = min(drinker.blood_volume + (1 * REM * seconds_per_tick), BLOOD_VOLUME_NORMAL) //Same as Bloody Mary, as it is roughly the same difficulty to make. Gives hemophages a bit more choices to supplant their blood levels. - +/* /datum/reagent/consumable/ethanol/abduction_fruit name = "Abduction Fruit" color = "#DEFACD" //(222, 250, 205) @@ -468,7 +466,7 @@ else quality = DRINK_GOOD return ..() - +*/ /datum/reagent/consumable/ethanol/bug_zapper name = "Bug Zapper" color = "#F5882A" //(222, 250, 205) @@ -484,7 +482,7 @@ desc = "An odd mix of copper, lemon juice and power meant for non-human consumption." /datum/reagent/consumable/ethanol/bug_zapper/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(isinsect(exposed_mob) || isflyperson(exposed_mob) || ismoth(exposed_mob)) + if(isinsectoid(exposed_mob) || isflyperson(exposed_mob) || ismoth(exposed_mob)) quality = RACE_DRINK else quality = DRINK_GOOD @@ -531,7 +529,7 @@ else quality = DRINK_GOOD return ..() - +/* /datum/reagent/consumable/ethanol/jell_wyrm name = "Jell Wyrm" color = "#FF6200" //(255, 98, 0) @@ -562,7 +560,7 @@ return ..() #undef JELLWYRM_DISGUST - +*/ /datum/reagent/consumable/ethanol/laval_spit //Yes Laval name = "Laval Spit" color = "#DE3009" //(222, 48, 9) @@ -583,7 +581,6 @@ else quality = DRINK_GOOD return ..() -*/ /datum/reagent/consumable/ethanol/frisky_kitty name = "Frisky Kitty" @@ -599,62 +596,13 @@ name = "cup of frisky kitty" desc = "Warm milk and some catnip." -/*/datum/reagent/consumable/ethanol/frisky_kitty/expose_mob(mob/living/exposed_mob, methods, reac_volume) +/datum/reagent/consumable/ethanol/frisky_kitty/expose_mob(mob/living/exposed_mob, methods, reac_volume) if(isfelinid(exposed_mob)) quality = RACE_DRINK else quality = DRINK_GOOD - return ..()*/ - -/* -/datum/reagent/consumable/ethanol/bloodshot_base - name = "Bloodshot Base" - description = "The bootleg blend of nutrients and alcohol that goes into making Bloodshots. Doesn't taste too great on its own, for Hemophages at least." - color = "#c29ca1" - boozepwr = 25 // Still more concentrated than in Bloodshot. - taste_description = "nutritious mix with an alcoholic kick to it" - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - - -/datum/reagent/consumable/ethanol/bloodshot - name = "Bloodshot" - description = "The history of the 'Bloodshot' is based in a mix of flavor-neutral chems devised to help deliver nutrients to a Hemophage's tumorous organs. Due to the expense of the real thing and the clinical nature of it, this liquor has been designed as a 'improvised' alternative; though, still tasting like a hangover cure. It smells like iron, giving a clue to the key ingredient." - color = "#a30000" - boozepwr = 20 // The only booze in it is Bloody Mary - taste_description = "blood filled to the brim with nutrients of all kinds" - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING - - -/datum/glass_style/drinking_glass/bloodshot - required_drink_type = /datum/reagent/consumable/ethanol/bloodshot - icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' - icon_state = "bloodshot" - name = "glass of bloodshot" - desc = "The history of the 'Bloodshot' is based in a mix of flavor-neutral chems devised to help deliver nutrients to a Hemophage's tumorous organs. Due to the expense of the real thing and the clinical nature of it, this liquor has been designed as a 'improvised' alternative; though, still tasting like a hangover cure. It smells like iron, giving a clue to the key ingredient." - - -#define BLOODSHOT_DISGUST 25 - -/datum/reagent/consumable/ethanol/bloodshot/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(ishemophage(exposed_mob)) - quality = RACE_DRINK - - else if(exposed_mob.blood_volume < exposed_mob.blood_volume_normal) - quality = DRINK_GOOD - - if(!quality) // Basically, you don't have a reason to want to have this in your system, it doesn't taste good to you! - exposed_mob.adjust_disgust(BLOODSHOT_DISGUST) - return ..() -#undef BLOODSHOT_DISGUST - -/datum/reagent/consumable/ethanol/bloodshot/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) - . = ..() - if(drinker.blood_volume < drinker.blood_volume_normal) - drinker.blood_volume = max(drinker.blood_volume, min(drinker.blood_volume + (3 * REM * seconds_per_tick), BLOOD_VOLUME_NORMAL)) //Bloodshot quickly restores blood loss. - /datum/reagent/consumable/ethanol/blizzard_brew name = "Blizzard Brew" description = "An ancient recipe. Served best chilled as much as dwarvenly possible." @@ -674,7 +622,7 @@ desc = "An ancient recipe. Served best chilled as much as dwarvenly possible." /datum/reagent/consumable/ethanol/blizzard_brew/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(isdwarf(exposed_mob)) + if(HAS_TRAIT(exposed_mob, TRAIT_SETTLER)) quality = RACE_DRINK else quality = DRINK_NICE @@ -711,7 +659,7 @@ desc = "Famously known to set beards aflame. Ingest at your own risk!" /datum/reagent/consumable/ethanol/molten_mead/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(isdwarf(exposed_mob)) + if(HAS_TRAIT(exposed_mob, TRAIT_SETTLER)) quality = RACE_DRINK else quality = DRINK_VERYGOOD @@ -721,7 +669,54 @@ drinker.adjust_fire_stacks(2) drinker.ignite_mob() ..() -*/ + +/datum/reagent/consumable/ethanol/bloodshot_base + name = "Bloodshot Base" + description = "The bootleg blend of nutrients and alcohol that goes into making Bloodshots. Doesn't taste too great on its own, for Hemophages at least." + color = "#c29ca1" + boozepwr = 25 // Still more concentrated than in Bloodshot. + taste_description = "nutritious mix with an alcoholic kick to it" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + + +/datum/reagent/consumable/ethanol/bloodshot + name = "Bloodshot" + description = "The history of the 'Bloodshot' is based in a mix of flavor-neutral chems devised to help deliver nutrients to a Hemophage's tumorous organs. Due to the expense of the real thing and the clinical nature of it, this liquor has been designed as a 'improvised' alternative; though, still tasting like a hangover cure. It smells like iron, giving a clue to the key ingredient." + color = "#a30000" + boozepwr = 20 // The only booze in it is Bloody Mary + taste_description = "blood filled to the brim with nutrients of all kinds" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + chemical_flags_doppler = REAGENT_BLOOD_REGENERATING + + +/datum/glass_style/drinking_glass/bloodshot + required_drink_type = /datum/reagent/consumable/ethanol/bloodshot + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "bloodshot" + name = "glass of bloodshot" + desc = "The history of the 'Bloodshot' is based in a mix of flavor-neutral chems devised to help deliver nutrients to a Hemophage's tumorous organs. Due to the expense of the real thing and the clinical nature of it, this liquor has been designed as a 'improvised' alternative; though, still tasting like a hangover cure. It smells like iron, giving a clue to the key ingredient." + +#define BLOODSHOT_DISGUST 25 + +/datum/reagent/consumable/ethanol/bloodshot/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(ishemophage(exposed_mob)) + quality = RACE_DRINK + + else if(exposed_mob.blood_volume < BLOOD_VOLUME_NORMAL) + quality = DRINK_GOOD + + if(!quality) // Basically, you don't have a reason to want to have this in your system, it doesn't taste good to you! + exposed_mob.adjust_disgust(BLOODSHOT_DISGUST) + + return ..() + +#undef BLOODSHOT_DISGUST + +/datum/reagent/consumable/ethanol/bloodshot/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() + if(drinker.blood_volume < BLOOD_VOLUME_NORMAL) + drinker.blood_volume = max(drinker.blood_volume, min(drinker.blood_volume + (3 * REM * seconds_per_tick), BLOOD_VOLUME_NORMAL)) //Bloodshot quickly restores blood loss. + /datum/reagent/consumable/ethanol/hippie_hooch name = "Hippie Hooch" description = "Peace and love! Under request of the HR department, this drink is sure to sober you up quickly." @@ -742,12 +737,12 @@ name = "glass of Hippie Hooch" desc = "Peace and love! Under request of the HR department, this drink is sure to sober you up quickly." -/*/datum/reagent/consumable/ethanol/hippie_hooch/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(isdwarf(exposed_mob)) +/datum/reagent/consumable/ethanol/hippie_hooch/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(HAS_TRAIT(exposed_mob, TRAIT_SETTLER)) quality = RACE_DRINK else quality = DRINK_FANTASTIC - return ..()*/ + return ..() /datum/reagent/consumable/ethanol/hippie_hooch/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) for(var/effect in status_effects_to_clear) @@ -772,12 +767,12 @@ name = "glass of Research Rum" desc = "Cooked up by dwarven scientists, this glowing pink brew is sure to supercharge your thinking. How? Science!" -/*/datum/reagent/consumable/ethanol/research_rum/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(isdwarf(exposed_mob)) +/datum/reagent/consumable/ethanol/research_rum/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(HAS_TRAIT(exposed_mob, TRAIT_SETTLER)) quality = RACE_DRINK else quality = DRINK_GOOD - return ..()*/ + return ..() /datum/reagent/consumable/ethanol/research_rum/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) . = ..() @@ -798,12 +793,12 @@ name = "glass of Golden Grog" desc = "A drink concocted by a dwarven Quartermaster who had too much time and money on his hands. Commonly ordered by influencers looking to flaunt their wealth." -/*/datum/reagent/consumable/ethanol/golden_grog/expose_mob(mob/living/exposed_mob, methods, reac_volume) - if(isdwarf(exposed_mob)) +/datum/reagent/consumable/ethanol/golden_grog/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(HAS_TRAIT(exposed_mob, TRAIT_SETTLER)) quality = RACE_DRINK else quality = DRINK_FANTASTIC - return ..()*/ + return ..() // RACIAL DRINKS END diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/datums/crafting/hemophage_recipes.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/datums/crafting/hemophage_recipes.dm new file mode 100644 index 0000000000000..eaa922b8f4217 --- /dev/null +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/datums/crafting/hemophage_recipes.dm @@ -0,0 +1,56 @@ +/datum/chemical_reaction/food/uncooked_blood_rice + required_reagents = list(/datum/reagent/consumable/rice = 10, /datum/reagent/blood = 20) + mix_message = "The rice absorbs the blood." + reaction_flags = REACTION_INSTANT + +/datum/chemical_reaction/food/uncooked_blood_rice/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) + var/location = get_turf(holder.my_atom) + for(var/i in 1 to created_volume) + new /obj/item/food/hemophage/blood_rice_pearl/raw(location) + +/datum/crafting_recipe/food/hemophage + category = CAT_HEMOPHAGE + +/datum/crafting_recipe/food/hemophage/blood_curd + name = "Blood Curd" + reqs = list( + /datum/reagent/blood = 20, + ) + result = /obj/item/food/hemophage/blood_curd + +/datum/crafting_recipe/food/hemophage/blood_noodles + name = "Raw Blood Noodles" + reqs = list( + /obj/item/food/spaghetti/raw = 1, + /datum/reagent/blood = 20, + ) + result = /obj/item/food/hemophage/blood_noodles/raw + +/datum/crafting_recipe/food/hemophage/boat_noodles + name = "Boat Noodles" + reqs = list( + /obj/item/food/hemophage/blood_noodles = 1, + /obj/item/food/hemophage/blood_curd = 1, + ) + result = /obj/item/food/hemophage/blood_noodles/boat_noodles + +/datum/crafting_recipe/food/hemophage/blood_cake + name = "Ti Hoeh Koe" + reqs = list( + /obj/item/food/boiledrice = 1, + /datum/reagent/blood = 20, + /datum/reagent/consumable/peanut_butter = 5, + ) + result = /obj/item/food/hemophage/blood_cake + +/datum/crafting_recipe/food/hemophage/blood_soup + name = "Dinuguan" + reqs = list( + /obj/item/food/hemophage/blood_curd = 1, + /obj/item/food/grown/chili = 1, + /obj/item/food/grown/garlic = 1, + /datum/reagent/blood = 20, + /datum/reagent/consumable/vinegar = 10, + /obj/item/reagent_containers/cup/bowl = 1, + ) + result = /obj/item/food/soup/hemophage/blood_soup diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks_recipes.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks_recipes.dm index 420fea48fb751..7605ae8ef8cd6 100644 --- a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks_recipes.dm +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks_recipes.dm @@ -340,7 +340,7 @@ /datum/reagent/consumable/milk = 1, ) required_temp = 296 //Just above room temp (22.85'C) -/* + /datum/chemical_reaction/drink/bloodshot_base results = list(/datum/reagent/consumable/ethanol/bloodshot_base = 2) required_reagents = list( @@ -356,7 +356,6 @@ /datum/reagent/consumable/ethanol/bloodshot_base = 2, ) reaction_tags = REACTION_TAG_DRINK | REACTION_TAG_EASY | REACTION_TAG_OTHER -*/ // Non-Booze diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/hemophage_food.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/hemophage_food.dm new file mode 100644 index 0000000000000..4f36f1872b3a1 --- /dev/null +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/hemophage_food.dm @@ -0,0 +1,117 @@ +/obj/item/food/hemophage + name = "bloody food" + desc = "If you see this, then something's gone very wrong and you should report it whenever you get the chance." + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/hemophage_food.dmi' + foodtypes = GORE | BLOODY + +/obj/item/food/hemophage/blood_rice_pearl + name = "kessen shinju" + desc = "A fun finger food. Little clumps of sticky rice with a bit of ground pork and green onion, all soaked and rolled in fresh blood; giving it a crimson hue. Recommended to serve hot!" + icon_state = "blood_rice_pearl" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/blood = 10, + ) + tastes = list("rice" = 3, "blood" = 5) + foodtypes = GRAIN | GORE | BLOODY + crafting_complexity = FOOD_COMPLEXITY_1 + +/obj/item/food/hemophage/blood_rice_pearl/raw + name = "uncooked blood rice" + desc = "A clump of raw rice, drenched in blood." + icon = 'icons/obj/food/food.dmi' + icon_state = "uncooked_rice" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/blood = 10, + ) + tastes = list("raw rice" = 3, "blood" = 5) + color = "#810000" + foodtypes = GRAIN | GORE | BLOODY | RAW + crafting_complexity = FOOD_COMPLEXITY_0 + +/obj/item/food/hemophage/blood_rice_pearl/raw/make_microwaveable() + AddElement(/datum/element/microwavable, /obj/item/food/hemophage/blood_rice_pearl) + +/obj/item/food/hemophage/blood_noodles + name = "boiled blood noodles" + desc = "A plain dish of blood-soaked noodles, it would probably be better with more ingredients." + icon = 'icons/obj/food/spaghetti.dmi' + icon_state = "spaghettiboiled" + color = "#810000" + food_reagents = list( + /datum/reagent/consumable/nutriment = 6, + /datum/reagent/blood = 20, + ) + tastes = list("blood" = 5, "pasta" = 1) + foodtypes = GRAIN | GORE | BLOODY + crafting_complexity = FOOD_COMPLEXITY_1 + +/obj/item/food/hemophage/blood_noodles/raw + name = "raw blood noodles" + desc = "Noodles thoroughly soaked in blood. Eating them raw doesn't sound appetizing. Nor does eating them at all, really." + color = "#ad0000" + food_reagents = list( + /datum/reagent/consumable/nutriment = 4, + /datum/reagent/blood = 15, // You should really be cooking those if you want the full amount of blood out of them + ) + tastes = list("blood" = 5, "raw pasta" = 1) + foodtypes = GRAIN | GORE | BLOODY | RAW + crafting_complexity = FOOD_COMPLEXITY_0 + +/obj/item/food/hemophage/blood_noodles/raw/make_microwaveable() + AddElement(/datum/element/microwavable, /obj/item/food/hemophage/blood_noodles) + +/obj/item/food/hemophage/blood_noodles/boat_noodles + name = "boat noodles" + desc = "A dish with normally made with a very strong combination of pork and beef; the main attraction in this meatless version being the combination of blood curds and curly noodles, seasoned and immersed in fresh blood to the point they've turned crimson. It reeks of iron." + icon_state = "meatballspaghetti" + color = "#d10000" + max_volume = 70 + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/consumable/nutriment/vitamin = 10, + /datum/reagent/blood = 50, + ) + tastes = list("blood" = 5, "congealed blood" = 3, "pasta" = 1) + foodtypes = GRAIN | GORE | BLOODY + crafting_complexity = FOOD_COMPLEXITY_2 + +/obj/item/food/hemophage/blood_curd + name = "blood curd" + desc = "Also known as 'blood tofu' or 'blood pudding,' this Yangyu delicacy looks to be made of congealed and cooked blood. It's soft and smooth, slightly chewy, and rich in riboflavin." + icon_state = "blood_curd" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 5, + /datum/reagent/blood = 15, + ) + tastes = list("congealed blood" = 1) + foodtypes = GORE | BLOODY | RAW + crafting_complexity = FOOD_COMPLEXITY_0 + +/obj/item/food/hemophage/blood_cake + name = "ti hoeh koe" + desc = "Also known as 'pig's blood cake' or 'black cake', this is a variant of blood pudding normally served as street food in night markets. Created from steamed blood and sticky rice, it's been coated in peanut powder and coriander, and can be served with some dipping sauces. It seems the amount of blood in this meal has been turned up a lot, giving the all-too-familiar twinge of iron when it's tasted." + icon_state = "blood_cake" + food_reagents = list( + /datum/reagent/consumable/nutriment = 10, + /datum/reagent/consumable/peanut_butter = 5, + /datum/reagent/blood = 25, + ) + tastes = list("blood" = 5, "crunchy rice" = 2, "peanut butter" = 2) + foodtypes = GRAIN | GORE | BLOODY | SUGAR | NUTS + crafting_complexity = FOOD_COMPLEXITY_3 + +/obj/item/food/soup/hemophage/blood_soup + name = "dinuguan" + desc = "A savory stew normally made of offal or freshly-simmered meat. This version features blood curds instead, while also featuring a rich, spicy and dark gravy made of fresh blood and vinegar. Chili and garlic were also added to enhance the savory flavor of the broth." + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/hemophage_food.dmi' + icon_state = "blood_soup" + max_volume = 90 + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/blood = 60, + ) + tastes = list("blood" = 5, "congealed blood" = 2, "chili" = 3, "vinegar" = 1, "garlic" = 1) + foodtypes = GORE | BLOODY | VEGETABLES + crafting_complexity = FOOD_COMPLEXITY_4 diff --git a/modular_doppler/modular_food_drinks_and_chems/icons/hemophage_food.dmi b/modular_doppler/modular_food_drinks_and_chems/icons/hemophage_food.dmi new file mode 100644 index 0000000000000..8c20579f46b62 Binary files /dev/null and b/modular_doppler/modular_food_drinks_and_chems/icons/hemophage_food.dmi differ diff --git a/modular_doppler/modular_mood/code/mood_events/dog_wag.dm b/modular_doppler/modular_mood/code/mood_events/dog_wag.dm new file mode 100644 index 0000000000000..58b00506ea146 --- /dev/null +++ b/modular_doppler/modular_mood/code/mood_events/dog_wag.dm @@ -0,0 +1,4 @@ +/datum/mood_event/dog_wag + description = "That wagging tail's excitement is infectious!" + mood_change = 1 + timeout = 30 SECONDS diff --git a/modular_doppler/modular_mood/code/mood_events/hotspring.dm b/modular_doppler/modular_mood/code/mood_events/hotspring.dm new file mode 100644 index 0000000000000..6a4c7eb154198 --- /dev/null +++ b/modular_doppler/modular_mood/code/mood_events/hotspring.dm @@ -0,0 +1,6 @@ +/datum/mood_event/hotspring + description = span_nicegreen("The water was enjoyably warm!\n") + mood_change = 4 + +/datum/mood_event/hotspring/nerfed + mood_change = 2 diff --git a/modular_doppler/modular_mood/code/mood_events/race_drink.dm b/modular_doppler/modular_mood/code/mood_events/race_drink.dm new file mode 100644 index 0000000000000..a697d94b8e44b --- /dev/null +++ b/modular_doppler/modular_mood/code/mood_events/race_drink.dm @@ -0,0 +1,4 @@ +/datum/mood_event/race_drink + description = span_nicegreen("That drink was made for me!\n") + mood_change = 12 + timeout = 9 MINUTES diff --git a/modular_doppler/modular_species/species_types/hemophage/_hemophage_defines.dm b/modular_doppler/modular_species/species_types/hemophage/_hemophage_defines.dm new file mode 100644 index 0000000000000..4398f79cf8ffb --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/_hemophage_defines.dm @@ -0,0 +1,18 @@ +/// Organ flag for organs of hemophage origin, or organs that have since been infected by an hemophage's tumor. +#define ORGAN_TUMOR_CORRUPTED (1<<12) // Not taking chances, hopefully this number remains good for a little while. + +/// We have a pulsating tumor, it's active. +#define PULSATING_TUMOR_ACTIVE 0 +/// We have a pulsating tumor, it's dormant. +#define PULSATING_TUMOR_DORMANT 1 +/// We don't have a pulsating tumor. +#define PULSATING_TUMOR_MISSING 2 + +/// Minimum amount of blood that you can reach via blood regeneration, regeneration will stop below this. +#define MINIMUM_VOLUME_FOR_REGEN (BLOOD_VOLUME_BAD + 1) // We do this to avoid any jankiness, and because we want to ensure that they don't fall into a state where they're constantly passing out in a locker. +/// Vomit flags for hemophages who eat food +#define HEMOPHAGE_VOMIT_FLAGS (MOB_VOMIT_MESSAGE | MOB_VOMIT_FORCE) +/// The ratio of reagents that get purged while a Hemophage vomits from trying to eat/drink something that their tumor doesn't like. +#define HEMOPHAGE_VOMIT_PURGE_RATIO 0.95 +/// How much disgust we're at after eating/drinking something the tumor doesn't like. +#define TUMOR_DISLIKED_FOOD_DISGUST DISGUST_LEVEL_GROSS + 15 diff --git a/modular_doppler/modular_species/species_types/hemophage/_organ_corruption.dm b/modular_doppler/modular_species/species_types/hemophage/_organ_corruption.dm new file mode 100644 index 0000000000000..4ada14828dd33 --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/_organ_corruption.dm @@ -0,0 +1,188 @@ +/// How long it takes for an organ to be corrupted by default. +#define BASE_TIME_BEFORE_CORRUPTION 60 SECONDS +/// The generic corrupted organ color. +#define GENERIC_CORRUPTED_ORGAN_COLOR "#333333" +/// How much organ damage do all corrupted organs take per second when the tumor is removed? +/// This will go by MUCH faster than you might expect, don't set it up too high. +#define CORRUPTED_ORGAN_DAMAGE_TUMORLESS 0.5 + +/// Component for Hemophage tumor-induced organ corruption, for the organs +/// that need to receive the `ORGAN_TUMOR_CORRUPTED` flag, to corrupt +/// them properly. +/datum/component/organ_corruption + dupe_mode = COMPONENT_DUPE_UNIQUE + /// The type of organ affected by this specific type of organ corruption. + var/corruptable_organ_type = /obj/item/organ/internal + /// If this type of organ has a unique sprite for what its corrupted + /// version should look like, this will be the icon file it will be pulled + /// from. + var/corrupted_icon = 'modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi' + /// If this type of organ has a unique sprite for what its corrupted + /// version should look like, this will be the icon state it will be pulled + /// from. + var/corrupted_icon_state = null + /// The timer associated with the corruption process, if any. + var/corruption_timer_id = null + /// The prefix added to the organ once it is successfully corrupted. + var/corrupted_prefix = "corrupted" + /// Whether this organ is tumorless and therefore should be taking damage. + /// Note: This variable isn't there to handle the behavior, and is only there + /// to prevent organs taking damage when tumors are being swapped around between + /// multiple people, somehow. + VAR_PROTECTED/currently_tumorless = FALSE + + +/datum/component/organ_corruption/Initialize(time_to_corrupt = BASE_TIME_BEFORE_CORRUPTION) + if(!istype(parent, corruptable_organ_type)) + return COMPONENT_INCOMPATIBLE + + if(time_to_corrupt <= 0) + corrupt_organ(parent) + return + + corruption_timer_id = addtimer(CALLBACK(src, PROC_REF(corrupt_organ), parent), time_to_corrupt, TIMER_STOPPABLE) + + +/datum/component/organ_corruption/RegisterWithParent() + if(corruption_timer_id) + RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(clear_corruption_timer)) + RegisterSignal(parent, COMSIG_ORGAN_REMOVED, PROC_REF(clear_corruption_timer)) + + +/datum/component/organ_corruption/UnregisterFromParent() + . = ..() + + UnregisterSignal(parent, list(COMSIG_ORGAN_IMPLANTED, COMSIG_ORGAN_REMOVED)) + + var/obj/item/organ/parent_organ = parent + if(istype(parent_organ) && parent_organ.owner) + UnregisterSignal(parent_organ.owner) + + clear_corruption_timer() + + +/// Handles clearing the timer for corrupting an organ if the organ is `QDELETING`. +/datum/component/organ_corruption/proc/clear_corruption_timer() + SIGNAL_HANDLER + + if(corruption_timer_id) + deltimer(corruption_timer_id) + + UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_ORGAN_REMOVED)) + + +/** + * Handles corrupting the organ, adding any sort of behavior on it as needed. + * + * Arguments: + * * corruption_target - The organ that will get corrupted. + */ +/datum/component/organ_corruption/proc/corrupt_organ(obj/item/organ/internal/corruption_target) + SHOULD_CALL_PARENT(TRUE) + if(!corruption_target) + return FALSE + + corruption_timer_id = null + corruption_target.organ_flags |= ORGAN_TUMOR_CORRUPTED + corruption_target.name = "[corrupted_prefix] [corruption_target.name]" + + if(corrupted_icon_state && corrupted_icon) + corruption_target.icon = corrupted_icon + corruption_target.icon_state = corrupted_icon_state + corruption_target.update_appearance() + + else + corruption_target.color = GENERIC_CORRUPTED_ORGAN_COLOR + + RegisterSignal(corruption_target, COMSIG_ORGAN_IMPLANTED, PROC_REF(register_signals_on_organ_owner)) + RegisterSignal(corruption_target, COMSIG_ORGAN_REMOVED, PROC_REF(unregister_signals_from_organ_loser), override = TRUE) + + return TRUE + + +/// Returns whether or not the attached organ has been corrupted yet or not. +/// Fancy wrapper for just `!corruption_timer_id`. +/datum/component/organ_corruption/proc/organ_is_corrupted() + return !corruption_timer_id + + +/** + * Handles registering signals on the (new) organ owner, if it was to ever be + * taken out and put into someone else. + */ +/datum/component/organ_corruption/proc/register_signals_on_organ_owner(obj/item/organ/implanted_organ, mob/living/carbon/receiver) + SIGNAL_HANDLER + SHOULD_CALL_PARENT(TRUE) + + if(implanted_organ != parent) + return FALSE + + RegisterSignal(receiver, COMSIG_PULSATING_TUMOR_REMOVED, PROC_REF(on_tumor_removed), override = TRUE) + UnregisterSignal(receiver, list(COMSIG_PULSATING_TUMOR_ADDED, COMSIG_LIVING_LIFE)) // In case there's a tumor transplant between Hemophages. + + return TRUE + + +/** + * Handles unregistering the signals that were registered on the `loser` from + * having this organ in their body. + */ +/datum/component/organ_corruption/proc/unregister_signals_from_organ_loser(obj/item/organ/target, mob/living/carbon/loser) + SIGNAL_HANDLER + SHOULD_CALL_PARENT(TRUE) + + if(target != parent) + return FALSE + + UnregisterSignal(loser, COMSIG_LIVING_LIFE) + + if(organ_is_corrupted()) + RegisterSignal(loser, COMSIG_PULSATING_TUMOR_ADDED, PROC_REF(on_tumor_reinserted), override = TRUE) + + return TRUE + + +/** + * Handles either deleting the corruption in case the organ didn't get corrupted + * yet, or starting the process of the organ degrading because the tumor isn't + * there to sustain it anymore. + */ +/datum/component/organ_corruption/proc/on_tumor_removed(mob/living/carbon/tumorless) + SIGNAL_HANDLER + + if(organ_is_corrupted()) + RegisterSignal(tumorless, COMSIG_LIVING_LIFE, PROC_REF(decay_corrupted_organ)) + currently_tumorless = TRUE + return + + qdel(src) + + +/datum/component/organ_corruption/proc/on_tumor_reinserted(mob/living/carbon/tumorful) + SIGNAL_HANDLER + + UnregisterSignal(tumorful, COMSIG_LIVING_LIFE) + currently_tumorless = FALSE + + +/** + * Handles damaging the corrupted organ when the tumor is no longer present in the body. + */ +/datum/component/organ_corruption/proc/decay_corrupted_organ(mob/living/carbon/tumorless, seconds_per_tick, times_fired) + SIGNAL_HANDLER + + var/obj/item/organ/corrupted_organ = parent + + if(!currently_tumorless && corrupted_organ.owner) + UnregisterSignal(corrupted_organ.owner, COMSIG_LIVING_LIFE) + + corrupted_organ.apply_organ_damage(CORRUPTED_ORGAN_DAMAGE_TUMORLESS * seconds_per_tick, required_organ_flag = ORGAN_TUMOR_CORRUPTED) + + if(corrupted_organ.organ_flags & ORGAN_FAILING && corrupted_organ.owner) + UnregisterSignal(corrupted_organ.owner, COMSIG_LIVING_LIFE) + + + +#undef BASE_TIME_BEFORE_CORRUPTION +#undef GENERIC_CORRUPTED_ORGAN_COLOR +#undef CORRUPTED_ORGAN_DAMAGE_TUMORLESS diff --git a/modular_doppler/modular_species/species_types/hemophage/atrophied_lungs.dm b/modular_doppler/modular_species/species_types/hemophage/atrophied_lungs.dm new file mode 100644 index 0000000000000..f83b2caaec56b --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/atrophied_lungs.dm @@ -0,0 +1,4 @@ + +/datum/component/organ_corruption/lungs + corruptable_organ_type = /obj/item/organ/internal/lungs + corrupted_prefix = "atrophied" diff --git a/modular_doppler/modular_species/species_types/hemophage/corrupted_liver.dm b/modular_doppler/modular_species/species_types/hemophage/corrupted_liver.dm new file mode 100644 index 0000000000000..bf0ffd10f309f --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/corrupted_liver.dm @@ -0,0 +1,55 @@ +/// The minimum ratio of reagents that need to have the `REAGENT_BLOOD_REGENERATING` chemical_flags for the Hemophage not to violently vomit upon consumption. +#define MINIMUM_BLOOD_REGENING_REAGENT_RATIO 0.75 + +/datum/component/organ_corruption/liver + corruptable_organ_type = /obj/item/organ/internal/liver + corrupted_icon_state = "liver" + + +/datum/component/organ_corruption/liver/register_signals_on_organ_owner(obj/item/organ/implanted_organ, mob/living/carbon/receiver) + if(!..()) + return + + RegisterSignal(receiver, COMSIG_GLASS_DRANK, PROC_REF(handle_drink)) + + +/datum/component/organ_corruption/liver/unregister_signals_from_organ_loser(obj/item/organ/target, mob/living/carbon/loser) + if(!..()) + return + + UnregisterSignal(loser, COMSIG_GLASS_DRANK) + + +/datum/component/organ_corruption/liver/UnregisterFromParent() + . = ..() + + var/obj/item/organ/internal/liver/parent_liver = parent + + if(parent_liver.owner) + UnregisterSignal(parent_liver.owner, COMSIG_GLASS_DRANK) + + +/** + * Handles reacting to drinks based on their content, to see if the tumor likes what's in it or not. + */ +/datum/component/organ_corruption/liver/proc/handle_drink(mob/living/target_mob, obj/item/reagent_containers/cup/container, mob/living/user) + SIGNAL_HANDLER + + if(HAS_TRAIT(target_mob, TRAIT_AGEUSIA)) // They don't taste anything, their body shouldn't react strongly to the taste of that stuff. + return + + if(container.reagents.has_chemical_flag_doppler(REAGENT_BLOOD_REGENERATING, container.reagents.total_volume * MINIMUM_BLOOD_REGENING_REAGENT_RATIO)) // At least 75% of the content of the cup needs to be something that's counting as blood-regenerating for the tumor not to freak out. + return + + var/mob/living/carbon/body = target_mob + ASSERT(istype(body)) + + body.set_disgust(max(body.disgust, TUMOR_DISLIKED_FOOD_DISGUST)) + + to_chat(body, span_warning("That tasted awful...")) + + // We don't lose nutrition because we don't even use nutrition as Hemopahges. It WILL however purge nearly all of what's in their stomach. + body.vomit(vomit_flags = HEMOPHAGE_VOMIT_FLAGS, lost_nutrition = 0, distance = 1, purge_ratio = HEMOPHAGE_VOMIT_PURGE_RATIO) + + +#undef MINIMUM_BLOOD_REGENING_REAGENT_RATIO diff --git a/modular_doppler/modular_species/species_types/hemophage/corrupted_stomach.dm b/modular_doppler/modular_species/species_types/hemophage/corrupted_stomach.dm new file mode 100644 index 0000000000000..3af433695a9af --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/corrupted_stomach.dm @@ -0,0 +1,45 @@ +/datum/component/organ_corruption/stomach + corruptable_organ_type = /obj/item/organ/internal/stomach + corrupted_icon_state = "stomach" + + +/datum/component/organ_corruption/stomach/corrupt_organ(obj/item/organ/internal/corruption_target) + . = ..() + + if(!.) + return + + RegisterSignal(corruption_target, COMSIG_STOMACH_AFTER_EAT, PROC_REF(on_stomach_after_eat)) + + +/datum/component/organ_corruption/stomach/UnregisterFromParent() + . = ..() + + UnregisterSignal(parent, COMSIG_STOMACH_AFTER_EAT) + + +/datum/component/organ_corruption/stomach/proc/on_stomach_after_eat(obj/item/organ/internal/stomach/tummy, atom/edible) + SIGNAL_HANDLER + + if(!istype(edible, /obj/item/food)) + return + + var/obj/item/food/eaten = edible + + if(BLOODY & eaten.foodtypes) // They're good if it's BLOODY food, they're less good if it isn't. + return + + var/obj/item/organ/internal/parent_organ = parent + + if(parent_organ.owner && HAS_TRAIT(parent_organ.owner, TRAIT_AGEUSIA)) // They don't taste anything, their body shouldn't react strongly to the taste of that stuff. + return + + var/mob/living/carbon/body = parent_organ.owner + ASSERT(istype(body)) + + body.set_disgust(max(body.disgust, TUMOR_DISLIKED_FOOD_DISGUST)) + + to_chat(body, span_warning("That tasted awful...")) + + // We don't lose nutrition because we don't even use nutrition as hemopahges. It WILL however purge nearly all of what's in their stomach. + body.vomit(vomit_flags = HEMOPHAGE_VOMIT_FLAGS, lost_nutrition = 0, distance = 1, purge_ratio = HEMOPHAGE_VOMIT_PURGE_RATIO) diff --git a/modular_doppler/modular_species/species_types/hemophage/corrupted_tongue.dm b/modular_doppler/modular_species/species_types/hemophage/corrupted_tongue.dm new file mode 100644 index 0000000000000..8afea50c35d52 --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/corrupted_tongue.dm @@ -0,0 +1,183 @@ +/// Maximum an Hemophage will drain, they will drain less if they hit their cap. +#define HEMOPHAGE_DRAIN_AMOUNT 50 +/// The multiplier for blood received by Hemophages out of humans with ckeys. +#define BLOOD_DRAIN_MULTIPLIER_CKEY 1.15 + +/datum/component/organ_corruption/tongue + corruptable_organ_type = /obj/item/organ/internal/tongue + corrupted_icon_state = "tongue" + /// The item action given to the tongue once it was corrupted. + var/tongue_action_type = /datum/action/cooldown/hemophage/drain_victim + + +/datum/component/organ_corruption/tongue/corrupt_organ(obj/item/organ/internal/corruption_target) + . = ..() + + if(!.) + return + + var/obj/item/organ/internal/tongue/corrupted_tongue = corruption_target + corrupted_tongue.liked_foodtypes = BLOODY + corrupted_tongue.disliked_foodtypes = NONE + + var/datum/action/tongue_action = corruption_target.add_item_action(tongue_action_type) + + if(corruption_target.owner) + tongue_action.Grant(corruption_target.owner) + + +/datum/action/cooldown/hemophage/drain_victim + name = "Drain Victim" + desc = "Leech blood from any carbon victim you are passively grabbing." + + +/datum/action/cooldown/hemophage/drain_victim/Activate(atom/target) + if(!iscarbon(owner)) + return + + var/mob/living/carbon/hemophage = owner + + if(!has_valid_target(hemophage)) + return + + // By now, we know that they're pulling a carbon. + drain_victim(hemophage, hemophage.pulling) + + +/** + * Handles the first checks to see if the target is eligible to be drained. + * + * Arguments: + * * hemophage - The person that's trying to drain something or someone else. + * + * Returns `TRUE` if the target is eligible to be drained, `FALSE` if not. + */ +/datum/action/cooldown/hemophage/drain_victim/proc/has_valid_target(mob/living/carbon/hemophage) + if(!hemophage.pulling || !iscarbon(hemophage.pulling) || isalien(hemophage.pulling)) + hemophage.balloon_alert(hemophage, "not pulling any valid target!") + return FALSE + + var/mob/living/carbon/victim = hemophage.pulling + if(hemophage.blood_volume >= BLOOD_VOLUME_MAXIMUM) + hemophage.balloon_alert(hemophage, "already full!") + return FALSE + + if(victim.stat == DEAD) + hemophage.balloon_alert(hemophage, "needs a living victim!") + return FALSE + + if(!victim.blood_volume || (victim.dna && ((HAS_TRAIT(victim, TRAIT_NOBLOOD)) || victim.dna.species.exotic_blood))) + hemophage.balloon_alert(hemophage, "[victim] doesn't have blood!") + return FALSE + + if(victim.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) + victim.show_message(span_warning("[hemophage] tries to bite you, but stops before touching you!")) + to_chat(hemophage, span_warning("[victim] is blessed! You stop just in time to avoid catching fire.")) + return FALSE + + if(victim.has_reagent(/datum/reagent/consumable/garlic)) + victim.show_message(span_warning("[hemophage] tries to bite you, but recoils in disgust!")) + to_chat(hemophage, span_warning("[victim] reeks of garlic! You can't bring yourself to drain such tainted blood.")) + return FALSE + + if(ismonkey(victim) && (hemophage.blood_volume >= BLOOD_VOLUME_NORMAL)) + hemophage.balloon_alert(hemophage, "their inferior blood cannot sate you any further!") + return FALSE + + return TRUE + + +/** + * The proc that actually handles draining the victim. Assumes that all the + * pre-requesite checks were made, and as such will not make any more checks + * outside of a `do_after` of three seconds. + * + * Arguments: + * * hemophage - The feeder. + * * victim - The one that's being drained. + */ +/datum/action/cooldown/hemophage/drain_victim/proc/drain_victim(mob/living/carbon/hemophage, mob/living/carbon/victim) + var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - hemophage.blood_volume //How much capacity we have left to absorb blood + // We start by checking that the victim is a human and they have a client, so we can give them the + // beneficial status effect for drinking higher-quality blood. + var/is_target_human_with_client = istype(victim, /mob/living/carbon/human) && victim.client + var/horrible_feeding = FALSE + + if(ismonkey(victim)) + is_target_human_with_client = FALSE // Sorry, not going to get the status effect from monkeys, even if they have a client in them. + hemophage.add_mood_event("gross_food", /datum/mood_event/disgust/hemophage_feed_monkey) // drinking from a monkey is inherently gross, like, REALLY gross + hemophage.adjust_disgust(TUMOR_DISLIKED_FOOD_DISGUST, DISGUST_LEVEL_MAXEDOUT) + blood_volume_difference = BLOOD_VOLUME_NORMAL - hemophage.blood_volume + horrible_feeding = TRUE + + if(istype(victim, /mob/living/carbon/human/species/monkey)) + is_target_human_with_client = FALSE // yep you're still not getting the status effect from humonkeys either. your tumour knows. + hemophage.add_mood_event("gross_food", /datum/mood_event/disgust/hemophage_feed_humonkey) + hemophage.adjust_disgust(DISGUST_LEVEL_GROSS / 4, TUMOR_DISLIKED_FOOD_DISGUST) // it's still gross but nowhere near as bad, though. + horrible_feeding = TRUE + + StartCooldown() + + if(!do_after(hemophage, 3 SECONDS, target = victim)) + hemophage.balloon_alert(hemophage, "stopped feeding") + return + + var/drained_blood = min(victim.blood_volume, HEMOPHAGE_DRAIN_AMOUNT, blood_volume_difference) + // if you drained from a human with a client, congrats + var/drained_multiplier = (is_target_human_with_client ? BLOOD_DRAIN_MULTIPLIER_CKEY : 1) + + var/obj/item/organ/internal/stomach/hemophage/stomach_reference = hemophage.get_organ_slot(ORGAN_SLOT_STOMACH) + if(isnull(stomach_reference)) + victim.blood_volume = clamp(victim.blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) + + else + if(!victim.transfer_blood_to(stomach_reference, drained_blood, forced = TRUE)) + victim.blood_volume = clamp(victim.blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) + hemophage.blood_volume = clamp(hemophage.blood_volume + (drained_blood * drained_multiplier), 0, BLOOD_VOLUME_MAXIMUM) + + log_combat(hemophage, victim, "drained [drained_blood]u of blood from", addition = " (NEW BLOOD VOLUME: [victim.blood_volume] cL)") + victim.show_message(span_danger("[hemophage] drains some of your blood!")) + + if(horrible_feeding) + if(istype(victim, /mob/living/carbon/human/species/monkey)) + to_chat(hemophage, span_notice("You take tentative draws of blood from [victim], each mouthful awash with the taste of ozone and a strange artificial twinge.")) + else + to_chat(hemophage, span_warning("You choke back tepid mouthfuls of foul blood from [victim]. The taste is absolutely vile.")) + else + to_chat(hemophage, span_notice("You pull greedy gulps of precious lifeblood from [victim]'s veins![is_target_human_with_client ? " That tasted particularly good!" : ""]")) + + playsound(hemophage, 'sound/items/drink.ogg', 30, TRUE, -2) + + // just let the hemophage know they're capped out on blood if they're trying to go for an exsanguinate and wondering why it isn't working + if(drained_blood != HEMOPHAGE_DRAIN_AMOUNT && hemophage.blood_volume >= (BLOOD_VOLUME_MAXIMUM - HEMOPHAGE_DRAIN_AMOUNT)) + to_chat(hemophage, span_boldnotice("Your thirst is temporarily slaked, and you can digest no more new blood for the moment.")) + + if(victim.blood_volume <= BLOOD_VOLUME_OKAY) + to_chat(hemophage, span_warning("That definitely left them looking pale...")) + to_chat(victim, span_warning("A groaning lethargy creeps into your muscles as you begin to feel slightly clammy...")) //let the victim know too + + if(is_target_human_with_client) + hemophage.apply_status_effect(/datum/status_effect/blood_thirst_satiated) + hemophage.add_mood_event("drank_human_blood", /datum/mood_event/hemophage_feed_human) // absolutely scrumptious + hemophage.clear_mood_event("gross_food") // it's a real palate cleanser, you know + hemophage.disgust *= 0.85 //also clears a little bit of disgust too + + // for this to ever occur, the hemophage actually has to be decently hungry, otherwise they'll cap their own blood reserves and be unable to pull it off. + if(!victim.blood_volume || victim.blood_volume <= BLOOD_VOLUME_SURVIVE) + to_chat(hemophage, span_boldwarning("Sensing an opportunity, your tumour produces an UNCONTROLLABLE URGE to draw extra deeply from [victim], forcing you to violently drain the last drops of blood from their veins.")) + to_chat(victim, span_bolddanger("The faint warmth of life flees your cooling body as [hemophage]'s ravenous hunger violently drains the last of your precious blood in one fell swoop, sending you hurtling headlong into the cold embrace of death.")) + victim.visible_message(span_warning("[victim] turns a terrible shade of ashen grey.")) + victim.death() + victim.blood_volume = 0 // the rest of the blood goes towards the tumour making happy juice for you. + if (is_target_human_with_client) + to_chat(hemophage, span_boldnotice("A rush of unbidden exhilaration surges through you as the predatory urges of your terrible coexistence are momentarily sated.")) + hemophage.add_mood_event("hemophage_killed", /datum/mood_event/hemophage_exsanguinate) // this is the tumour-equivalent of a nice little headpat. you murderer, you! + hemophage.disgust = 0 // all is forgiven. + if(prob(33)) + to_chat(hemophage, span_warning("...what have you done?")) + else if ((victim.blood_volume + HEMOPHAGE_DRAIN_AMOUNT) <= BLOOD_VOLUME_SURVIVE) + to_chat(hemophage, span_warning("A sense of hesitation gnaws: you know for certain that taking much more blood from [victim] WILL kill them. ...but another part of you sees only opportunity.")) + + +#undef HEMOPHAGE_DRAIN_AMOUNT +#undef BLOOD_DRAIN_MULTIPLIER_CKEY diff --git a/modular_doppler/modular_species/species_types/hemophage/hemophage_actions.dm b/modular_doppler/modular_species/species_types/hemophage/hemophage_actions.dm new file mode 100644 index 0000000000000..9feaeb41ca98b --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/hemophage_actions.dm @@ -0,0 +1,57 @@ +/// The message displayed in the hemophage's chat when they enter their dormant state. +#define DORMANT_STATE_START_MESSAGE "You feel your tumor's pulse slowing down, as it enters a dormant state. You suddenly feel incredibly weak and vulnerable to everything, and exercise has become even more difficult, as only your most vital bodily functions remain." +/// The message displayed in the hemophage's chat when they leave their dormant state. +#define DORMANT_STATE_END_MESSAGE "You feel a rush through your veins, as you can tell your tumor is pulsating at a regular pace once again. You no longer feel incredibly vulnerable, and exercise isn't as difficult anymore." + + +/datum/action/cooldown/hemophage + cooldown_time = 3 SECONDS + button_icon_state = null + + +/datum/action/cooldown/hemophage/New(Target) + . = ..() + + if(target && isnull(button_icon_state)) + AddComponent(/datum/component/action_item_overlay, target) + + +/datum/action/cooldown/hemophage/toggle_dormant_state + name = "Enter Dormant State" + desc = "Causes the tumor inside of you to enter a dormant state, causing it to need just a minimum amount of blood to survive. However, as the tumor living in your body is the only thing keeping you still alive, rendering it latent cuts both it and you to just the essential functions to keep standing. It will no longer mend your body even in the darkness nor allow you to taste anything, and the lack of blood pumping through you will have you the weakest you've ever felt; and leave you hardly able to run. It is not on a switch, and it will take some time for it to awaken." + cooldown_time = 2 MINUTES + + +/datum/action/cooldown/hemophage/toggle_dormant_state/Activate(atom/action_target) + if(!owner || !ishuman(owner) || !target) + return + + var/obj/item/organ/internal/heart/hemophage/tumor = target + if(!tumor || !istype(tumor)) // This shouldn't happen, but you can never be too careful. + return + + owner.balloon_alert(owner, "[tumor.is_dormant ? "leaving" : "entering"] dormant state") + + if(!do_after(owner, 3 SECONDS)) + owner.balloon_alert(owner, "cancelled state change") + return + + to_chat(owner, span_notice("[tumor.is_dormant ? DORMANT_STATE_END_MESSAGE : DORMANT_STATE_START_MESSAGE]")) + + StartCooldown() + + tumor.toggle_dormant_state() + tumor.toggle_dormant_tumor_vulnerabilities(owner) + + if(tumor.is_dormant) + name = "Exit Dormant State" + desc = "Causes the pitch-black mass living inside of you to awaken, allowing your circulation to return and blood to pump freely once again. It fills your legs to let you run again, and longs for the darkness as it did before. You start to feel strength rather than the weakness you felt before. However, the tumor giving you life is not on a switch, and it will take some time to subdue it again." + else + name = initial(name) + desc = initial(desc) + + build_all_button_icons(UPDATE_BUTTON_NAME) + + +#undef DORMANT_STATE_START_MESSAGE +#undef DORMANT_STATE_END_MESSAGE diff --git a/modular_doppler/modular_species/species_types/hemophage/hemophage_hud.dm b/modular_doppler/modular_species/species_types/hemophage/hemophage_hud.dm new file mode 100644 index 0000000000000..493bca308be95 --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/hemophage_hud.dm @@ -0,0 +1,22 @@ +/// 1 tile down +#define UI_BLOOD_DISPLAY "WEST:6,CENTER-1:0" + +///Maptext define for Hemophage HUDs +#define FORMAT_HEMOPHAGE_HUD_TEXT(valuecolor, value) MAPTEXT("
[trunc(value)]
") + +/atom/movable/screen/hemophage + icon = 'modular_doppler/modular_species/species_types/hemophage/icons/hemophage_hud.dmi' + +/atom/movable/screen/hemophage/blood + name = "Blood Meter" + icon_state = "blood_display" + screen_loc = UI_BLOOD_DISPLAY + +/atom/movable/screen/hemophage/blood/proc/update_blood_hud(blood_volume) + maptext = FORMAT_HEMOPHAGE_HUD_TEXT(hud_text_color(), blood_volume) + +/atom/movable/screen/hemophage/blood/proc/hud_text_color(blood_volume) + return blood_volume > BLOOD_VOLUME_SAFE ? "#FFDDDD" : "#FFAAAA" + +#undef UI_BLOOD_DISPLAY +#undef FORMAT_HEMOPHAGE_HUD_TEXT diff --git a/modular_doppler/modular_species/species_types/hemophage/hemophage_moods.dm b/modular_doppler/modular_species/species_types/hemophage/hemophage_moods.dm new file mode 100644 index 0000000000000..5b9ed1438db41 --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/hemophage_moods.dm @@ -0,0 +1,24 @@ +/datum/mood_event/hemophage_feed_human + description = "I slaked my hunger on fresh, vital blood. That felt good!" + mood_change = 2 + timeout = 5 MINUTES + +/datum/mood_event/disgust/hemophage_feed_monkey + description = "I had to feed off a gibbering monkey... what have I become?" + mood_change = -4 + timeout = 5 MINUTES + +/datum/mood_event/disgust/hemophage_feed_humonkey + description = "Somehow I know deep down that humonkey blood is no substitute for the real thing..." + mood_change = -1 + timeout = 5 MINUTES + +/datum/mood_event/disgust/hemophage_feed_synthesized_blood + description = "My last blood meal was artificial and tasted... wrong." + mood_change = -2 + timeout = 5 MINUTES + +// Killing someone via hemophage exsanguination gives you a mood buff for the rest of the round. +/datum/mood_event/hemophage_exsanguinate + description = "I drained someone of all their blood... why do I feel so giddy?" + mood_change = 4 diff --git a/modular_doppler/modular_species/species_types/hemophage/hemophage_organs.dm b/modular_doppler/modular_species/species_types/hemophage/hemophage_organs.dm new file mode 100644 index 0000000000000..3811d6d4b24ed --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/hemophage_organs.dm @@ -0,0 +1,113 @@ +/// Generic description for the corrupted organs that don't have one. +#define GENERIC_CORRUPTED_ORGAN_DESC "This shares the shape of a normal organ, but it's been covered and filled with some sort of midnight-black pulsing tissue, engorged with some sort of infectious mass." + +/// The rate at which blood metabolizes in a Hemophage's stomach subtype. +#define BLOOD_METABOLIZATION_RATE (0.1 * REAGENTS_METABOLISM) +/// Defines the time for making a corrupted organ start off corrupted. +#define ORGAN_CORRUPTION_INSTANT 0 + + +/obj/item/organ/internal/liver/hemophage + name = "liver" // Name change is handled by /datum/component/organ_corruption/corrupt_organ() + desc = GENERIC_CORRUPTED_ORGAN_DESC + icon = 'modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi' + organ_flags = ORGAN_EDIBLE | ORGAN_TUMOR_CORRUPTED + + +/obj/item/organ/internal/liver/hemophage/Initialize(mapload) + . = ..() + + AddComponent(/datum/component/organ_corruption/liver, time_to_corrupt = ORGAN_CORRUPTION_INSTANT) + +/obj/item/organ/internal/liver/hemophage/handle_chemical(mob/living/carbon/affected_mob, datum/reagent/chem, seconds_per_tick, times_fired) + . = ..() + + // parent returned COMSIG_MOB_STOP_REAGENT_CHECK or we are failing + if((. & COMSIG_MOB_STOP_REAGENT_CHECK) || (organ_flags & ORGAN_FAILING)) + return + + // hemophages drink blood so blood must be pretty good for them + if(!istype(chem, /datum/reagent/blood)) + return + + var/feedback_delivered = FALSE + for(var/datum/wound/iter_wound as anything in affected_mob.all_wounds) + if(!SPT_PROB(5, seconds_per_tick)) + continue + + var/helped = iter_wound.blood_life_process() + if(feedback_delivered || !helped) + continue + + to_chat(affected_mob, span_notice("A euphoric feeling hits you as blood's warmth washes through your insides. Your body feels more alive, your wounds healthier.")) + feedback_delivered = TRUE + + +// Different handling, different name. +// Returns FALSE by default so broken bones and 'loss' wounds don't give a false message +/datum/wound/proc/blood_life_process() + return FALSE + +// Slowly increase (gauzed) clot rate, better than tea. +/datum/wound/pierce/bleed/blood_life_process() + gauzed_clot_rate += 0.1 + return TRUE + +// Slowly increase clot rate, better than tea. +/datum/wound/slash/flesh/blood_life_process() + clot_rate += 0.2 + return TRUE + +// Brought over from tea as well. +/datum/wound/burn/flesh/blood_life_process() + // Sanitizes and heals, but with a limit + if(flesh_healing <= 0.1) + flesh_healing += 0.02 + infestation_rate = max(infestation_rate - 0.005, 0) + return TRUE + + +/obj/item/organ/internal/stomach/hemophage + name = "stomach" // Name change is handled by /datum/component/organ_corruption/corrupt_organ() + desc = GENERIC_CORRUPTED_ORGAN_DESC + icon = 'modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi' + organ_flags = ORGAN_EDIBLE | ORGAN_TUMOR_CORRUPTED + + +/obj/item/organ/internal/stomach/hemophage/Initialize(mapload) + . = ..() + + AddComponent(/datum/component/organ_corruption/stomach, time_to_corrupt = ORGAN_CORRUPTION_INSTANT) + + +// I didn't feel like moving this behavior onto the component, it was just too annoying to do. +/obj/item/organ/internal/stomach/hemophage/on_life(seconds_per_tick, times_fired) + var/datum/reagent/blood/blood = reagents.has_reagent(/datum/reagent/blood) + if(blood) + blood.metabolization_rate = BLOOD_METABOLIZATION_RATE + var/blood_DNA = blood.data["blood_DNA"] + if(!blood_DNA) //does the blood we're digesting have any DNA? if it doesn't, it's artificial, and that's gross.. + src.owner.adjust_disgust(DISGUST_LEVEL_GROSS / 16, DISGUST_LEVEL_VERYGROSS) + src.owner.add_mood_event("gross_food", /datum/mood_event/disgust/hemophage_feed_synthesized_blood) + + return ..() + + +/obj/item/organ/internal/tongue/hemophage + name = "tongue" // Name change is handled by /datum/component/organ_corruption/corrupt_organ() + desc = GENERIC_CORRUPTED_ORGAN_DESC + icon = 'modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi' + organ_flags = ORGAN_EDIBLE | ORGAN_TUMOR_CORRUPTED + liked_foodtypes = BLOODY + disliked_foodtypes = NONE + + +/obj/item/organ/internal/tongue/hemophage/Initialize(mapload) + . = ..() + + AddComponent(/datum/component/organ_corruption/tongue, time_to_corrupt = ORGAN_CORRUPTION_INSTANT) + + +#undef GENERIC_CORRUPTED_ORGAN_DESC +#undef BLOOD_METABOLIZATION_RATE +#undef ORGAN_CORRUPTION_INSTANT diff --git a/modular_doppler/modular_species/species_types/hemophage/hemophage_species.dm b/modular_doppler/modular_species/species_types/hemophage/hemophage_species.dm new file mode 100644 index 0000000000000..abd3d59bd5e15 --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/hemophage_species.dm @@ -0,0 +1,173 @@ +/// Some starter text sent to the Hemophage initially, because Hemophages have shit to do to stay alive. +#define HEMOPHAGE_SPAWN_TEXT "You are an [span_danger("Hemophage")]. You will slowly but constantly lose blood if outside of a closet-like object. If inside a closet-like object, or in pure darkness, you will slowly heal, at the cost of blood. You may gain more blood by grabbing a live victim and using your drain ability." + + +/datum/species/hemophage + name = "Hemophage" + id = SPECIES_HEMOPHAGE + inherent_traits = list( + TRAIT_ADVANCEDTOOLUSER, + TRAIT_CAN_STRIP, + TRAIT_NOHUNGER, + TRAIT_NOBREATH, + TRAIT_OXYIMMUNE, + TRAIT_VIRUSIMMUNE, + TRAIT_LITERATE, + TRAIT_DRINKS_BLOOD, + TRAIT_USES_SKINTONES, + ) + inherent_biotypes = MOB_HUMANOID | MOB_ORGANIC + exotic_bloodtype = "U" + mutantheart = /obj/item/organ/internal/heart/hemophage + mutantliver = /obj/item/organ/internal/liver/hemophage + mutantstomach = /obj/item/organ/internal/stomach/hemophage + mutanttongue = /obj/item/organ/internal/tongue/hemophage + mutantlungs = null + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT + examine_limb_id = SPECIES_HUMAN + skinned_type = /obj/item/stack/sheet/animalhide/human + +/datum/species/hemophage/check_roundstart_eligible() + if(check_holidays(HALLOWEEN)) + return TRUE + + return ..() + +/datum/species/hemophage/on_species_gain(mob/living/carbon/human/new_hemophage, datum/species/old_species, pref_load) + . = ..() + to_chat(new_hemophage, HEMOPHAGE_SPAWN_TEXT) + new_hemophage.update_body() + +/datum/species/hemophage/get_species_description() + return "Oftentimes feared or pushed out of society for the predatory nature of their condition, \ + Hemophages are typically mixed around various Frontier populations, keeping their true nature hidden while \ + reaping both the benefits and easy access to prey, enjoying unpursued existences on the Frontier." + + +/datum/species/hemophage/get_species_lore() + return list( + "Though known by many other names, 'Hemophages' are those that have found themselves the host of a bloodthirsty infection. 'Natural' hemophages have their infection first overtake their body through the bloodstream, though methods vary; \ + Hemophages thought to be a dense cluster of tightly related but distinct strains and variants. It will first take root in the chest, making alterations to the cells making up the host's organs to rapidly expand and take them over. \ + Lungs will deflate into nothingness, the liver becomes wrapped up and filled with corrupted tissue and the digestive organs will gear themselves towards the intake of the only meal they can have; blood. The host's heart will almost triple in size from this 'cancerous' tissue, forming an overgrown coal-black tumor that now keeps their body standing.", + + "The initial infection process in someone becoming a Hemophage can have varied effects and impacts, though there is a sort of timeline that crops up in the vast majority of cases. The process often begins with the steady decline of the host's heartrate into severe bradycardial agony as it begins to become choked with tumor tissue, chest pains and lightheadedness signaling the first stretch. \ + Fatigue, exercise intolerance, and near-fainting persist and worsen as the host's lungs slowly begin to atrophy; the second organ to normally be 'attacked' by the process. Coughing and hemoptysis will worsen and worsen until it suddenly stops, alongside the Hemophage's ability and need to continue breathing altogether.", + + "The ability to eat normal food becomes psychologically intolerable quickly after the infection fully takes root in their central nervous system, the tumor no longer holding interest in anything it cannot derive nutrients from. Foods once enjoyed by the host begin to taste completely revolting, many quickly developing an aversion to even try chewing it. \ + However, new desires quickly begin to form, the host's whole suite of senses rapidly adapting to a keen interest in blood. Hyperosmia in specific kicks in, the iron-tinged scent of a bleeder provoking and agitating hunger like the smell of any fresh cooking would for a human. \ + Not all blood aids the host the same. Its currently thought that a Hemophage is capable at a subconscious level of recognizing and differentiating different sources of blood, and the tumor within hijacking their psychology to prioritize blood from creatures it is able to reproduce inside of. \ + Blood from animals is reported to 'be like trying to subsist on milk or whipped cream or heavily fluffed up bread,' harder to digest, taste, or enjoy, necessitating the Hemophage to drink far more of it just to get the same value from a relatively small amount of human blood. \ + 'Storebought' blood, like from refrigerated medical blood bags, is reported to 'taste thin,' like a heavily watered down drink. Only the physical, predatory act of drinking blood fresh from another humanoid is enough to properly 'sate' the tumor, ticking the right psychological and physiological boxes to be fully digested and enjoyed. \ + The sensation is like nothing else, being extremely pleasurable for the host; even if they don't want it to be.", + + "Photosensitivity of the skin develops. While light artificial or not won't harm them, it's noted that Hemophages seem to be far more comfortable in any level of darkness, their skin and eyes far more sensitive than before to UV. \ + When taken away from it, and ideally isolated from higher-than-average levels of radiation such as found in orbital habitats, it's noted that the host's body will begin to reconstruct with aid from the tumor inside. Flesh will knit back together, burns will rapidly cast off, and even scar tissue will properly regenerate into normal tissue. \ + It's thought that this process is even delicate enough to work at the host's DNA, prolonging epigenetic maintenance and ensuring biological systems remain healthy and youthful for far longer than normal. \ + Given that Hemophages are converted and kept alive by their infection, it will ruthlessly fight off foreign bacteria, viruses, and tissues, repurposing or annihilating them to ensure there's no 'competition' over its host. \ + Notably, the host's blood will turn almost black like ink due to their blood becoming more 'dense', yet no longer carrying nearly as much oxygen. Due to this hemophages are known to look more 'ashen', their lips often turning to a dark gray, and their skin going more pale than it normally would be. \ + Their tongues, not an organ the tumor spares, additionally turn a pure black; in some cases, the sclera in their eyes might even turn significantly dark especially when bloodshot.", + + "The psychology of Hemophages is well-studied in the psychiatric field. Over time, hemophages have developed a plethora of conditioned responses and quirks just as humans, their prey, have. \ + The first few years after a Hemophage is 'changed' is often enough to drive them over the edge. In some cases, the first few days. The process of being turned is a series of traumas in quick succession as the host is often made to murder. \ + The lucky have a 'moral' source of blood at hand and whoever 'converted' them to guide them through it; the unlucky have to scramble to maintain their sense of self as they become something else. \ + The physical sensation of first infection is often painful, often terrifying, and often grotesque to experience as the host feels their body shocked with vicious tumor tissue and their mind warped by near-death stretching over potentially days. \ + Some snap, some grow stone-hard, but it's rare to actually meet a Hemophage that remembers the process. Some hemophages are born into their condition; the infection staying dormant until the child is a few months to a year old, ensuring their stability in being able to handle the tumor. \ + These hosts tend to live extremely tumultuous childhoods, simply not being strong enough to feed on anything but the weakest of creatures, and trending towards immense loneliness from the high visibility of their condition's treatment during youth.", + + "The hunger is the main driver for these sordid creatures. From the very moment they wake back up from the process of being 'changed,' a powerful hunger is awakened. It twists and throbs in their heart, drowning out coherent thought. \ + During the 'semi-starvation' phase in humans, the changes are dramatic. Significant decreases in strength and stamina, body temperature, heart rate, and obsession with food. Dreaming and fantasizing about it, reading and talking about it, and savoring what little meals they can get access to; hoarding it for themselves and eating to the last crumb. \ + In Hemophages, this response is heavily similar, but turned outwards. The hunger of a Hemophage is psychologically pressing in nearly every way, detracting from all other concerns in an urge to be dealt with as soon as it can be. \ + Panic easily sets in during these times of famine, the host instinctually knowing that it must be sated or the tumor within them will soon run out of blood to feed on, which would result in their mutual death. \ + Even the very sight and smell of fresh blood can push a Hemophage into this kind of state if they haven't fed in awhile, only drinking from a living creature or intense meditation and concentration being able to push it down.", + + "Socially, Hemophages are mostly solitary hunters. It is extremely easy for them to recognize each other; the unique smell of their blackened ichor, the subtle details of their body and the way it moves, the shallow or nonexistant breathing, or even the likely smell of multiple victims' blood on their breath. \ + Even normal humans report talking to known Hemophages being psychologically unsettling, linked to being armed with the knowledge that they've likely taken several lives and might take theirs. \ + This predatory aura surrounding them tends to leave them operating primarily solitarily; always passively running threat analysis on others of their kind, especially given the higher 'value' of their more nutrient-rich blood. \ + When they do choose to work together, Hemophages gather in groups of no more than ten. Any more, and their activities would surely be impossible to disguise.", + + "'Conversion' tends to be uncommon for Hemophages. The typical line of thought is that one 'wouldn't want to raise a kid every time they go out for dinner,' as the 'creation' of a new Hemophage involves, essentially, becoming an on-site therapist and mentor for an unspecified amount of time. \ + It's often not worth the risk to potentially allow a fresh 'convert' to gain access to a Hemophage's identity if they're attempting to 'blend', and to potentially turn on them and expose their illegal activities. \ + However the infection which creates them, like any living creature, has a drive to procreate regardless; often the urge to spread it overtakes a hemophage's sensibilities anyway, and some are known to serially infect others simply to 'stir the pot.", + + "In terms of human society, it's known for Hemophages to be passively strangled by the law itself. In 'civilized' places like Sol, Hemophages that attack or kill humans for their blood are prosecuted heavily; almost disproportionately compared to if the same crimes were committed by a normal person. \ + Artificial sources of blood are intentionally kept rare by pharmaceutical companies, and those that do end up getting an easier access to such sources seem to almost always be working in the Medical field. \ + Even adopting pets is made nigh-on-impossible for them. Those that don't leave to places like frontier systems typically end up part of oft-ephemeral networks of others of their kind, offering time-sensitive advice on where certain 'low-risk' or 'less-than-legal' meals may be found and forcing themselves to work past their base instincts to cooperate to an extent; anything else would mean death." + ) + + +/datum/species/hemophage/prepare_human_for_preview(mob/living/carbon/human/human) + human.skin_tone = "albino" + human.hair_color = "#1d1d1d" + human.hairstyle = "Pompadour (Big)" + regenerate_organs(human, src, visual_only = TRUE) + human.update_body(TRUE) + +/datum/species/hemophage/create_pref_unique_perks() + var/list/to_add = list() + + to_add += list( + list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = "moon", + SPECIES_PERK_NAME = "Darkness Affinity", + SPECIES_PERK_DESC = "A Hemophage is only at home in the darkness, the infection \ + within a Hemophage seeking to return them to a healthy state \ + whenever it can be in the shadow. However, light artificial or \ + otherwise irritates their bodies and the cancer keeping them alive, \ + not harming them but keeping them from regenerating. Modern \ + Hemophages have been known to use lockers as a convenient \ + source of darkness, while the extra protection they provide \ + against background radiations allows their tumor to avoid \ + having to expend any blood to maintain minimal bodily functions \ + so long as their host remains stationary in said locker.", + ), + list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = "biohazard", + SPECIES_PERK_NAME = "Viral Symbiosis", + SPECIES_PERK_DESC = "Hemophages, due to their condition, cannot get infected by \ + other viruses and don't actually require an external source of oxygen \ + to stay alive.", + ), + list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = "tint", + SPECIES_PERK_NAME = "The Thirst", + SPECIES_PERK_DESC = "In place of eating, Hemophages suffer from the Thirst, caused by their tumor. \ + Thirst of what? Blood! Their tongue allows them to grab people and drink \ + their blood, and they will suffer severe consequences if they run out. As a note, \ + it doesn't matter whose blood you drink, it will all be converted into your blood \ + type when consumed. That being said, the blood of other sentient humanoids seems \ + to quench their Thirst for longer than otherwise-acquired blood would.", + ), + ) + + return to_add + + +/datum/species/hemophage/create_pref_blood_perks() + var/list/to_add = list() + + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = "tint", + SPECIES_PERK_NAME = "Universal Blood", + SPECIES_PERK_DESC = "[plural_form] have blood that appears to be an amalgamation of all other \ + blood types, made possible thanks to some special antigens produced by \ + their tumor, making them able to receive blood of any other type, so \ + long as it is still human-like blood.", + ), + ) + + return to_add + +/datum/species/hemophage/get_cry_sound(mob/living/carbon/human/hemophage) + var/datum/species/human/human_species = GLOB.species_prototypes[/datum/species/human] + return human_species.get_cry_sound(hemophage) + +// We don't need to mention that they're undead, as the perks that come from it are otherwise already explicited, and they might no longer be actually undead from a gameplay perspective, eventually. +/datum/species/hemophage/create_pref_biotypes_perks() + return + + +#undef HEMOPHAGE_SPAWN_TEXT diff --git a/modular_doppler/modular_species/species_types/hemophage/hemophage_status_effects.dm b/modular_doppler/modular_species/species_types/hemophage/hemophage_status_effects.dm new file mode 100644 index 0000000000000..5d46ecf0dd2ef --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/hemophage_status_effects.dm @@ -0,0 +1,164 @@ +/// How much brute damage their body regenerates per second while using blood regeneration. +#define BLOOD_REGEN_BRUTE_AMOUNT 2 +/// How much burn damage their body regenerates per second while using blood regeneration. +#define BLOOD_REGEN_BURN_AMOUNT 2 +/// How much toxin damage their body regenerates per second while using blood regeneration. +#define BLOOD_REGEN_TOXIN_AMOUNT 1.5 +/// How much cellular damage their body regenerates per second while using blood regeneration. +#define BLOOD_REGEN_CELLULAR_AMOUNT 1.50 + +/datum/status_effect/blood_thirst_satiated + id = "blood_thirst_satiated" + duration = 30 MINUTES + status_type = STATUS_EFFECT_REFRESH + alert_type = /atom/movable/screen/alert/status_effect/blood_thirst_satiated + /// What will the bloodloss_speed_multiplier of the Hemophage be changed by upon receiving this status effect? + var/bloodloss_speed_multiplier = 0.5 + + +/datum/status_effect/blood_thirst_satiated/on_apply() + // This status effect should not exist on its own, or on a non-human. + if(!owner || !ishuman(owner)) + return FALSE + + var/obj/item/organ/internal/heart/hemophage/tumor_heart = owner.get_organ_by_type(/obj/item/organ/internal/heart/hemophage) + + if(!tumor_heart) + return FALSE + + tumor_heart.bloodloss_rate *= bloodloss_speed_multiplier + + return TRUE + + +/datum/status_effect/blood_thirst_satiated/on_remove() + // This status effect should not exist on its own, or on a non-human. + if(!owner || !ishuman(owner)) + return + + var/obj/item/organ/internal/heart/hemophage/tumor_heart = owner.get_organ_by_type(/obj/item/organ/internal/heart/hemophage) + + if(!tumor_heart) + return + + tumor_heart.bloodloss_rate /= bloodloss_speed_multiplier + + +/datum/status_effect/blood_regen_active + id = "blood_regen_active" + status_type = STATUS_EFFECT_UNIQUE + processing_speed = STATUS_EFFECT_NORMAL_PROCESS + alert_type = /atom/movable/screen/alert/status_effect/blood_regen_active + /// Current multiplier for how much blood they spend healing themselves for every point of damage healed. + var/blood_to_health_multiplier = 0.25 + + +/datum/status_effect/blood_regen_active/on_apply() + // This status effect should not exist on its own, or on a non-human. + if(!owner || !ishuman(owner)) + return FALSE + + to_chat(owner, span_notice("You feel the tumor inside you pulse faster as the absence of light eases its work, allowing it to knit your flesh and reconstruct your body.")) + + return TRUE + + +// This code also had to be copied over from /datum/action/item_action to ensure that we could display the heart in the alert. +/datum/status_effect/blood_regen_active/on_creation(mob/living/new_owner, ...) + . = ..() + if(!.) + return + + if(!linked_alert) + return + + var/obj/item/organ/internal/heart/hemophage/tumor_heart = owner.get_organ_by_type(/obj/item/organ/internal/heart/hemophage) + if(tumor_heart) + var/old_layer = tumor_heart.layer + var/old_plane = tumor_heart.plane + // reset the x & y offset so that item is aligned center + tumor_heart.pixel_x = 0 + tumor_heart.pixel_y = 0 + tumor_heart.layer = FLOAT_LAYER // They need to be displayed on the proper layer and plane to show up on the button. We elevate them temporarily just to steal their appearance, and then revert it. + tumor_heart.plane = FLOAT_PLANE + linked_alert.cut_overlays() + linked_alert.add_overlay(tumor_heart) + tumor_heart.layer = old_layer + tumor_heart.plane = old_plane + + return . + + +/datum/status_effect/blood_regen_active/on_remove() + // This status effect should not exist on its own. + if(!owner) + return + + to_chat(owner, span_notice("You feel the pulse of the tumor in your chest returning back to normal.")) + + +/datum/status_effect/blood_regen_active/tick(seconds_between_ticks) + var/mob/living/carbon/human/regenerator = owner + + var/max_blood_for_regen = regenerator.blood_volume - MINIMUM_VOLUME_FOR_REGEN + var/blood_used = NONE + + var/brutes_to_heal = NONE + var/brute_damage = regenerator.getBruteLoss() + + // We have to check for the damaged bodyparts like this as well, to account for robotic bodyparts, as we don't want to heal those. Stupid, I know, but that's the best proc we got to check that currently. + if(brute_damage && length(regenerator.get_damaged_bodyparts(brute = TRUE, burn = FALSE, required_bodytype = BODYTYPE_ORGANIC))) + brutes_to_heal = min(max_blood_for_regen, min(BLOOD_REGEN_BRUTE_AMOUNT, brute_damage) * seconds_between_ticks) + blood_used += brutes_to_heal * blood_to_health_multiplier + max_blood_for_regen -= brutes_to_heal * blood_to_health_multiplier + + var/burns_to_heal = NONE + var/burn_damage = regenerator.getFireLoss() + + if(burn_damage && max_blood_for_regen > NONE && length(regenerator.get_damaged_bodyparts(brute = FALSE, burn = TRUE, required_bodytype = BODYTYPE_ORGANIC))) + burns_to_heal = min(max_blood_for_regen, min(BLOOD_REGEN_BURN_AMOUNT, burn_damage) * seconds_between_ticks) + blood_used += burns_to_heal * blood_to_health_multiplier + max_blood_for_regen -= burns_to_heal * blood_to_health_multiplier + + if(brutes_to_heal || burns_to_heal) + regenerator.heal_overall_damage(brutes_to_heal, burns_to_heal, NONE, BODYTYPE_ORGANIC) + + var/toxin_damage = regenerator.getToxLoss() + + if(toxin_damage && max_blood_for_regen > NONE) + var/toxins_to_heal = min(max_blood_for_regen, min(BLOOD_REGEN_TOXIN_AMOUNT, toxin_damage) * seconds_between_ticks) + blood_used += toxins_to_heal * blood_to_health_multiplier + max_blood_for_regen -= toxins_to_heal * blood_to_health_multiplier + regenerator.adjustToxLoss(-toxins_to_heal) + + if(!blood_used) + regenerator.remove_status_effect(/datum/status_effect/blood_regen_active) + return + + regenerator.blood_volume = max(regenerator.blood_volume - blood_used, MINIMUM_VOLUME_FOR_REGEN) + + +/datum/movespeed_modifier/hemophage_dormant_state + id = "hemophage_dormant_state" + multiplicative_slowdown = 2 // Yeah, they'll be quite significantly slower when in their dormant state. + blacklisted_movetypes = FLOATING|FLYING + + +/atom/movable/screen/alert/status_effect/blood_thirst_satiated + name = "Thirst Satiated" + desc = "Substitutes and taste-thin imitations keep your pale body standing, but nothing abates eternal thirst and slakes the infection quite like the real thing: Hot blood from a real sentient being." + icon = 'icons/effects/bleed.dmi' + icon_state = "bleed10" + + +/atom/movable/screen/alert/status_effect/blood_regen_active + name = "Enhanced Regeneration" + desc = "Being in a sufficiently dark location allows your tumor to allocate more energy to enhancing your body's natural regeneration, at the cost of blood volume proportional to the damage healed." + icon = 'icons/hud/screen_alert.dmi' + icon_state = "template" + + +#undef BLOOD_REGEN_BRUTE_AMOUNT +#undef BLOOD_REGEN_BURN_AMOUNT +#undef BLOOD_REGEN_TOXIN_AMOUNT +#undef BLOOD_REGEN_CELLULAR_AMOUNT diff --git a/modular_doppler/modular_species/species_types/hemophage/hemophage_tumor.dm b/modular_doppler/modular_species/species_types/hemophage/hemophage_tumor.dm new file mode 100644 index 0000000000000..b8b4861353019 --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/hemophage_tumor.dm @@ -0,0 +1,171 @@ +/// Minimum amount of light for Hemophages to be considered in pure darkness, and therefore be allowed to heal just like in a closet. +#define MINIMUM_LIGHT_THRESHOLD_FOR_REGEN 0.1 + +/// How high should the damage multiplier to the Hemophage be when they're in a dormant state? +#define DORMANT_DAMAGE_MULTIPLIER 3 +/// By how much the blood drain will be divided when the tumor is in a dormant state. +#define DORMANT_BLOODLOSS_MULTIPLIER 10 +/// How much blood do Hemophages normally lose per second (visible effect is every two seconds, so twice this value). +#define NORMAL_BLOOD_DRAIN 0.05 + +/// Just a conversion factor that ensures there's no weird floating point errors when blood is draining. +#define FLOATING_POINT_ERROR_AVOIDING_FACTOR 1000 + +/// Trait gained from the pulsating tumor. +#define TRAIT_TUMOR "tumor" + + +/obj/item/organ/internal/heart/hemophage + name = "pulsating tumor" + icon = 'modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi' + icon_state = "tumor-on" + base_icon_state = "tumor" + desc = "This pulsating organ nearly resembles a normal heart, but it's been twisted beyond any human appearance, having turned to the color of coal. The way it barely fits where the original organ was sends shivers down your spine... The fact that it's what keeps them alive makes it all the more terrifying." + actions_types = list(/datum/action/cooldown/hemophage/toggle_dormant_state) + /// Are we currently dormant? Defaults to PULSATING_TUMOR_ACTIVE (so FALSE). + var/is_dormant = PULSATING_TUMOR_ACTIVE + /// What is the current rate (per second) at which the tumor is consuming blood? + var/bloodloss_rate = NORMAL_BLOOD_DRAIN + /// hud element to display our blood level + var/atom/movable/screen/hemophage/blood/blood_tracker + +/obj/item/organ/internal/heart/hemophage/Insert(mob/living/carbon/tumorful, special, movement_flags) + . = ..() + if(!. || !owner) + return + + SEND_SIGNAL(tumorful, COMSIG_PULSATING_TUMOR_ADDED, tumorful) + tumorful.AddElement(/datum/element/tumor_corruption) + RegisterSignal(tumorful, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item)) + +/obj/item/organ/internal/heart/hemophage/Remove(mob/living/carbon/tumorless, special = FALSE, movement_flags) + . = ..() + + SEND_SIGNAL(tumorless, COMSIG_PULSATING_TUMOR_REMOVED, tumorless) + tumorless.RemoveElement(/datum/element/tumor_corruption) + tumorless.remove_status_effect(/datum/status_effect/blood_thirst_satiated) + UnregisterSignal(tumorless, COMSIG_MOB_GET_STATUS_TAB_ITEMS) + + if(!ishuman(tumorless)) + return + + var/mob/living/carbon/human/tumorless_human = tumorless + + // We make sure to account for dormant tumor vulnerabilities, so that we don't achieve states that shouldn't be possible. + if(is_dormant) + toggle_dormant_state() + toggle_dormant_tumor_vulnerabilities(tumorless_human) + tumorless_human.remove_movespeed_modifier(/datum/movespeed_modifier/hemophage_dormant_state) + + if(tumorless_human.hud_used) + var/datum/hud/hud_used = tumorless_human.hud_used + + hud_used.infodisplay -= blood_tracker + QDEL_NULL(blood_tracker) + +/obj/item/organ/internal/heart/hemophage/on_life(seconds_per_tick, times_fired) + . = ..() + + // A Hemophage's tumor will be able to be operated on multiple times, so + // they are not entirely dependant on Xenobiology when they die more than + // once. + // It's intended that you can't print a tumor, because why would you? + operated = FALSE + + if(blood_tracker) + blood_tracker.update_blood_hud(owner.blood_volume) + // create the hud + else if(owner.hud_used) + var/datum/hud/hud_used = owner.hud_used + blood_tracker = new /atom/movable/screen/hemophage/blood(null, hud_used) + hud_used.infodisplay += blood_tracker + + owner.hud_used.show_hud(owner.hud_used.hud_version) + + if(can_heal_owner_damage()) + owner.apply_status_effect(/datum/status_effect/blood_regen_active) + + else + owner.remove_status_effect(/datum/status_effect/blood_regen_active) + + if(in_closet(owner)) // No regular bloodloss if you're in a closet + return + + owner.blood_volume = (owner.blood_volume * FLOATING_POINT_ERROR_AVOIDING_FACTOR - bloodloss_rate * seconds_per_tick * FLOATING_POINT_ERROR_AVOIDING_FACTOR) / FLOATING_POINT_ERROR_AVOIDING_FACTOR + + if(owner.blood_volume <= BLOOD_VOLUME_SURVIVE) + to_chat(owner, span_danger("You ran out of blood!")) + owner.investigate_log("starved to death from lack of blood caused by [src].", INVESTIGATE_DEATHS) + owner.death() // Owch! Ran out of blood. + + +/// Simple helper proc that toggles the dormant state of the tumor, which also switches its appearance to reflect said change. +/obj/item/organ/internal/heart/hemophage/proc/toggle_dormant_state() + is_dormant = !is_dormant + base_icon_state = is_dormant ? "[base_icon_state]-dormant" : initial(base_icon_state) + + bloodloss_rate *= is_dormant ? 1 / DORMANT_BLOODLOSS_MULTIPLIER : DORMANT_BLOODLOSS_MULTIPLIER + + update_appearance() + + if(isnull(owner)) + return + + if(is_dormant) + owner.add_movespeed_modifier(/datum/movespeed_modifier/hemophage_dormant_state) + ADD_TRAIT(owner, TRAIT_AGEUSIA, TRAIT_TUMOR) + + else + owner.remove_movespeed_modifier(/datum/movespeed_modifier/hemophage_dormant_state) + REMOVE_TRAIT(owner, TRAIT_AGEUSIA, TRAIT_TUMOR) + + +/// Simple helper proc that returns whether or not the given hemophage is in a closet subtype (but not in any bodybag subtype). +/obj/item/organ/internal/heart/hemophage/proc/in_closet(mob/living/carbon/human/hemophage) + return istype(hemophage.loc, /obj/structure/closet) && !istype(hemophage.loc, /obj/structure/closet/body_bag) + + +/// Simple helper proc that returns whether or not the given hemophage is in total darkness. +/obj/item/organ/internal/heart/hemophage/proc/in_total_darkness(mob/living/carbon/human/hemophage) + var/turf/current_turf = get_turf(hemophage) + if(!istype(current_turf)) + return FALSE + + return current_turf.get_lumcount() <= MINIMUM_LIGHT_THRESHOLD_FOR_REGEN + + +/// Whether or not we should be applying the healing status effect for the owner. +/obj/item/organ/internal/heart/hemophage/proc/can_heal_owner_damage() + // We handle the least expensive checks first. + if(owner.health >= owner.maxHealth || is_dormant || owner.blood_volume <= MINIMUM_VOLUME_FOR_REGEN || (!in_closet(owner) && !in_total_darkness(owner))) + return FALSE + + return owner.getBruteLoss() || owner.getFireLoss() || owner.getToxLoss() + + +/// Simple helper to toggle the hemophage's vulnerability (or lack thereof) based on the status of their tumor. +/// This proc contains no check whatsoever, to avoid redundancy of null checks and such. +/// That being said, it shouldn't be used by anything but the tumor, if you have to call it outside of that, you probably have gone wrong somewhere. +/obj/item/organ/internal/heart/hemophage/proc/toggle_dormant_tumor_vulnerabilities(mob/living/carbon/human/hemophage) + var/datum/physiology/hemophage_physiology = hemophage.physiology + var/damage_multiplier = is_dormant ? DORMANT_DAMAGE_MULTIPLIER : 1 / DORMANT_DAMAGE_MULTIPLIER + + hemophage_physiology.brute_mod *= damage_multiplier + hemophage_physiology.burn_mod *= damage_multiplier + hemophage_physiology.tox_mod *= damage_multiplier + hemophage_physiology.stamina_mod *= damage_multiplier / 2 // Doing half here so that they don't instantly hit stam-crit when hit like only once. + + +/obj/item/organ/internal/heart/hemophage/proc/get_status_tab_item(mob/living/source, list/items) + SIGNAL_HANDLER + items += "Current blood level: [owner.blood_volume]/[BLOOD_VOLUME_MAXIMUM]" + +#undef MINIMUM_LIGHT_THRESHOLD_FOR_REGEN + +#undef FLOATING_POINT_ERROR_AVOIDING_FACTOR + +#undef DORMANT_DAMAGE_MULTIPLIER +#undef DORMANT_BLOODLOSS_MULTIPLIER +#undef NORMAL_BLOOD_DRAIN + +#undef TRAIT_TUMOR diff --git a/modular_doppler/modular_species/species_types/hemophage/icons/hemophage_hud.dmi b/modular_doppler/modular_species/species_types/hemophage/icons/hemophage_hud.dmi new file mode 100644 index 0000000000000..5a2105e6dff65 Binary files /dev/null and b/modular_doppler/modular_species/species_types/hemophage/icons/hemophage_hud.dmi differ diff --git a/modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi b/modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi new file mode 100644 index 0000000000000..bb8c725c64396 Binary files /dev/null and b/modular_doppler/modular_species/species_types/hemophage/icons/hemophage_organs.dmi differ diff --git a/modular_doppler/modular_species/species_types/hemophage/tumor_corruption.dm b/modular_doppler/modular_species/species_types/hemophage/tumor_corruption.dm new file mode 100644 index 0000000000000..df18d12b264c5 --- /dev/null +++ b/modular_doppler/modular_species/species_types/hemophage/tumor_corruption.dm @@ -0,0 +1,81 @@ +/// Element that handles spreading the Hemophages' pulsating tumor corruption +/// to applicable organs, so that they can be properly corrupted, even if they +/// weren't roundstart-corrupted organs. +/datum/element/tumor_corruption + + +/datum/element/tumor_corruption/Attach(datum/target) + . = ..() + + if(.) + return + + if(!iscarbon(target)) + return ELEMENT_INCOMPATIBLE + + handle_organ_corruption_on_existing_organs(target) + RegisterSignal(target, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(try_corrupt_new_organ)) + + +/datum/element/tumor_corruption/Detach(datum/source, ...) + . = ..() + + UnregisterSignal(source, COMSIG_CARBON_GAIN_ORGAN) + + +/** + * Handles corrupting already-existing organs upon having the tumor be inserted in the mob. + */ +/datum/element/tumor_corruption/proc/handle_organ_corruption_on_existing_organs(mob/living/carbon/organ_enjoyer) + var/obj/item/organ/internal/liver/liver = organ_enjoyer.get_organ_slot(ORGAN_SLOT_LIVER) + if(liver && !(liver.organ_flags & ORGAN_TUMOR_CORRUPTED)) + liver.AddComponent(/datum/component/organ_corruption/liver) + + var/obj/item/organ/internal/lungs/lungs = organ_enjoyer.get_organ_slot(ORGAN_SLOT_LUNGS) + if(lungs && !(lungs.organ_flags & ORGAN_TUMOR_CORRUPTED)) + lungs.AddComponent(/datum/component/organ_corruption/lungs) + + var/obj/item/organ/internal/stomach/stomach = organ_enjoyer.get_organ_slot(ORGAN_SLOT_STOMACH) + if(stomach && !(stomach.organ_flags & ORGAN_TUMOR_CORRUPTED)) + stomach.AddComponent(/datum/component/organ_corruption/stomach) + + var/obj/item/organ/internal/tongue/tongue = organ_enjoyer.get_organ_slot(ORGAN_SLOT_TONGUE) + if(tongue && !(tongue.organ_flags & ORGAN_TUMOR_CORRUPTED)) + tongue.AddComponent(/datum/component/organ_corruption/tongue) + + +/** + * Handles corrupting any new organ that's inserted into the affected mob, if needed. + */ +/datum/element/tumor_corruption/proc/try_corrupt_new_organ(mob/living/carbon/receiver, obj/item/organ/new_organ) + SIGNAL_HANDLER + + var/static/list/corruptable_organ_slots = list( + ORGAN_SLOT_LIVER, + ORGAN_SLOT_LUNGS, + ORGAN_SLOT_STOMACH, + ORGAN_SLOT_TONGUE, + ) + + if(!(new_organ.slot in corruptable_organ_slots)) + return + + if(new_organ.organ_flags & ORGAN_TUMOR_CORRUPTED) + return + + switch(new_organ.slot) + if(ORGAN_SLOT_LIVER) + new_organ.AddComponent(/datum/component/organ_corruption/liver) + return + + if(ORGAN_SLOT_LUNGS) + new_organ.AddComponent(/datum/component/organ_corruption/lungs) + return + + if(ORGAN_SLOT_STOMACH) + new_organ.AddComponent(/datum/component/organ_corruption/stomach) + return + + if(ORGAN_SLOT_TONGUE) + new_organ.AddComponent(/datum/component/organ_corruption/tongue) + return diff --git a/tgstation.dme b/tgstation.dme index 00a956e83e12e..3f9582b44c288 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -404,6 +404,7 @@ #include "code\__DEFINES\~doppler_defines\banning.dm" #include "code\__DEFINES\~doppler_defines\cells.dm" #include "code\__DEFINES\~doppler_defines\colony_fabricator_misc.dm" +#include "code\__DEFINES\~doppler_defines\construction.dm" #include "code\__DEFINES\~doppler_defines\declarations.dm" #include "code\__DEFINES\~doppler_defines\DNA.dm" #include "code\__DEFINES\~doppler_defines\enterprise_resource_planning.dm" @@ -422,6 +423,7 @@ #include "code\__DEFINES\~doppler_defines\organ_slots.dm" #include "code\__DEFINES\~doppler_defines\preferences.dm" #include "code\__DEFINES\~doppler_defines\reagent_forging_tools.dm" +#include "code\__DEFINES\~doppler_defines\reagents.dm" #include "code\__DEFINES\~doppler_defines\reskin_defines.dm" #include "code\__DEFINES\~doppler_defines\say.dm" #include "code\__DEFINES\~doppler_defines\signals.dm" @@ -6771,10 +6773,12 @@ #include "modular_doppler\modular_customization\tri_color\tri_color_prefs_bespoke.dm" #include "modular_doppler\modular_customization\tri_color\wings.dm" #include "modular_doppler\modular_food_drinks_and_chems\chemistry_reagents.dm" -#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\alcohol reagents.dm" +#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\alcohol_reagents.dm" #include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\drink_reagents.dm" #include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\drinks.dm" #include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\drinks_recipes.dm" +#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\hemophage_food.dm" +#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\datums\crafting\hemophage_recipes.dm" #include "modular_doppler\modular_hydroponics\code\honeysuckle.dm" #include "modular_doppler\modular_hydroponics\code\tea_coffee.dm" #include "modular_doppler\modular_items\bettermed_overrides.dm" @@ -6801,6 +6805,9 @@ #include "modular_doppler\modular_medical\wounds\wound_effects.dm" #include "modular_doppler\modular_mob_spawn\code\mob_spawn.dm" #include "modular_doppler\modular_mood\code\mood_events\brushed.dm" +#include "modular_doppler\modular_mood\code\mood_events\dog_wag.dm" +#include "modular_doppler\modular_mood\code\mood_events\hotspring.dm" +#include "modular_doppler\modular_mood\code\mood_events\race_drink.dm" #include "modular_doppler\modular_quirks\excitable\quirk.dm" #include "modular_doppler\modular_quirks\overwrites\musician.dm" #include "modular_doppler\modular_quirks\paycheck_rations\code\quirk.dm" @@ -6819,6 +6826,20 @@ #include "modular_doppler\modular_species\species_types\ethereal\ethereal.dm" #include "modular_doppler\modular_species\species_types\genemod\genemod.dm" #include "modular_doppler\modular_species\species_types\golem\golem.dm" +#include "modular_doppler\modular_species\species_types\hemophage\_hemophage_defines.dm" +#include "modular_doppler\modular_species\species_types\hemophage\_organ_corruption.dm" +#include "modular_doppler\modular_species\species_types\hemophage\atrophied_lungs.dm" +#include "modular_doppler\modular_species\species_types\hemophage\corrupted_liver.dm" +#include "modular_doppler\modular_species\species_types\hemophage\corrupted_stomach.dm" +#include "modular_doppler\modular_species\species_types\hemophage\corrupted_tongue.dm" +#include "modular_doppler\modular_species\species_types\hemophage\hemophage_actions.dm" +#include "modular_doppler\modular_species\species_types\hemophage\hemophage_hud.dm" +#include "modular_doppler\modular_species\species_types\hemophage\hemophage_moods.dm" +#include "modular_doppler\modular_species\species_types\hemophage\hemophage_organs.dm" +#include "modular_doppler\modular_species\species_types\hemophage\hemophage_species.dm" +#include "modular_doppler\modular_species\species_types\hemophage\hemophage_status_effects.dm" +#include "modular_doppler\modular_species\species_types\hemophage\hemophage_tumor.dm" +#include "modular_doppler\modular_species\species_types\hemophage\tumor_corruption.dm" #include "modular_doppler\modular_species\species_types\insectoid\insectoid.dm" #include "modular_doppler\modular_species\species_types\insectoid\insectoid_bodyparts.dm" #include "modular_doppler\modular_species\species_types\lizardpeople\lizardpeople.dm" diff --git a/tgui/packages/tgui/interfaces/PersonalCrafting.tsx b/tgui/packages/tgui/interfaces/PersonalCrafting.tsx index f4c4cd8bf0ed9..6e474f5215251 100644 --- a/tgui/packages/tgui/interfaces/PersonalCrafting.tsx +++ b/tgui/packages/tgui/interfaces/PersonalCrafting.tsx @@ -23,6 +23,7 @@ import { Food } from './PreferencesMenu/data'; const TYPE_ICONS = { 'Can Make': 'utensils', [Food.Alcohol]: 'wine-glass', + [Food.Bloody]: 'tint', // DOPPLER EDIT ADDITION - Hemophage Food [Food.Breakfast]: 'sun', [Food.Bugs]: 'bug', [Food.Cloth]: 'tshirt', @@ -76,6 +77,7 @@ const CATEGORY_ICONS_COOKING = { Cakes: 'cake-candles', 'Egg-Based Food': 'egg', Frozen: 'ice-cream', + 'Hemophage Food': 'tint', // DOPPLER EDIT ADDITION - Hemophage Food 'Lizard Food': 'dragon', Meats: 'bacon', 'Mexican Food': 'pepper-hot', diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts index 3f3a341bcffdd..246f6ffac993a 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts @@ -26,6 +26,7 @@ export enum Food { Sugar = 'SUGAR', Toxic = 'TOXIC', Vegetables = 'VEGETABLES', + Bloody = 'BLOODY', // DOPPLER EDIT ADDITION - Hemophage Food } export enum JobPriority {