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("