diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index a16b19ed4ad..94bd86920fe 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -114,3 +114,10 @@ #define COMSIG_ATOM_TIMESTOP_FREEZE "atom_timestop_freeze" /// when the timestop ability effect ends on the atom: (datum/proximity_monitor/advanced/timestop) #define COMSIG_ATOM_TIMESTOP_UNFREEZE "atom_timestop_unfreeze" + +/// when atom falls onto the floor and become exposed to germs: (datum/component/germ_exposure) +#define COMSIG_ATOM_GERM_EXPOSED "atom_germ_exposed" +/// when atom is picked up from the floor or moved to an elevated structure: (datum/component/germ_exposure) +#define COMSIG_ATOM_GERM_UNEXPOSED "atom_germ_unexposed" +/// when atom is washed +#define COMSIG_ATOM_WASHED "atom_washed" diff --git a/code/__DEFINES/dcs/signals/signals_food.dm b/code/__DEFINES/dcs/signals/signals_food.dm index 4b7f773138e..36a8b7b3392 100644 --- a/code/__DEFINES/dcs/signals/signals_food.dm +++ b/code/__DEFINES/dcs/signals/signals_food.dm @@ -9,6 +9,8 @@ #define COMSIG_FOOD_CROSSED "food_crossed" /// From base of Component/edible/On_Consume: (mob/living/eater, mob/living/feeder) #define COMSIG_FOOD_CONSUMED "food_consumed" +/// called when a pill is injested (mob/living/eater, mob/living/feeder) +#define COMSIG_PILL_CONSUMED "pill_consumed" /// called when an item is used as an ingredient: (atom/customized) #define COMSIG_ITEM_USED_AS_INGREDIENT "item_used_as_ingredient" /// called when an edible ingredient is added: (datum/component/edible/ingredient) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 9863d429bda..f48d46dbb3c 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -837,6 +837,8 @@ GLOBAL_LIST_INIT(layers_to_offset, list( #define VOMIT_TOXIC 1 /// The mob will vomit a purple color #define VOMIT_PURPLE 2 +/// The mob will vomit a nebula color +#define VOMIT_NEBULA 3 /// Possible value of [/atom/movable/buckle_lying]. If set to a different (positive-or-zero) value than this, the buckling thing will force a lying angle on the buckled. #define NO_BUCKLE_LYING -1 diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index aa38a7d95b8..bebb599bd9a 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -1142,3 +1142,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define SPEAKING_FROM_TONGUE "tongue" ///trait source that sign language should use #define SPEAKING_FROM_HANDS "hands" + +///Trait given by /datum/component/germ_sensitive +#define TRAIT_GERM_SENSITIVE "germ_sensitive" diff --git a/code/_globalvars/lists/typecache.dm b/code/_globalvars/lists/typecache.dm index 0436b4d3b08..786036b58b1 100644 --- a/code/_globalvars/lists/typecache.dm +++ b/code/_globalvars/lists/typecache.dm @@ -17,12 +17,27 @@ GLOBAL_LIST_INIT(typecache_machine_or_structure, typecacheof(list( /// A typecache listing structures that are considered to have surfaces that you can place items on that are higher than the floor. This, of course, should be restricted to /atom/movables. This is primarily used for food decomposition code. GLOBAL_LIST_INIT(typecache_elevated_structures, typecacheof(list( /obj/machinery/conveyor, - /obj/machinery/stasis, /obj/structure/closet, /obj/structure/rack, /obj/structure/table, + //Kitchen /obj/machinery/smartfridge, /obj/machinery/smartfridge/drying_rack, // Redundant, given above, but this is for the sake of explicitness. + /obj/structure/bonfire, + /obj/machinery/grill, + /obj/machinery/griddle, + /obj/machinery/deepfryer, + /obj/machinery/processor, + /obj/machinery/microwave, + /obj/machinery/oven, + /obj/machinery/stove, + /obj/machinery/gibber, + //Botany + /obj/machinery/hydroponics, // So that harvest doesn't catch germs or decompose (includes dirt piles) + //Medbay + /obj/machinery/stasis, + /obj/machinery/chem_master, // Pills may catch germs + /obj/machinery/plumbing/pill_press, ))) /// A typecache of objects that player controlled, easily accessible, hostile mobs should not be able to attack diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index b81b12fe9ec..0d35407ca55 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -226,6 +226,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING, "TRAIT_UNCATCHABLE" = TRAIT_UNCATCHABLE, "TRAIT_DANGEROUS_OBJECT" = TRAIT_DANGEROUS_OBJECT, + "TRAIT_GERM_SENSITIVE" = TRAIT_GERM_SENSITIVE, ), /atom = list( "TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER, diff --git a/code/datums/components/food/decomposition.dm b/code/datums/components/food/decomposition.dm index 9f5d0f0a2ac..a3850ab39a6 100644 --- a/code/datums/components/food/decomposition.dm +++ b/code/datums/components/food/decomposition.dm @@ -4,6 +4,7 @@ #define DECOMPOSITION_TIME_RAW (5 MINUTES) #define DECOMPOSITION_TIME_GROSS (7 MINUTES) +///Makes things decompose when exposed to germs. Requires /datum/component/germ_sensitive to detect exposure. /datum/component/decomposition dupe_mode = COMPONENT_DUPE_UNIQUE /// Makes sure maploaded food only starts decomposing if a player's EVER picked it up before @@ -30,7 +31,7 @@ var/obj/effect/abstract/particle_holder/particle_effect /datum/component/decomposition/Initialize(mapload, decomp_req_handle, decomp_flags = NONE, decomp_result, ant_attracting = FALSE, custom_time = 0, stink_particles = /particles/stink) - if(!ismovable(parent)) + if(!ismovable(parent) || !HAS_TRAIT(parent, TRAIT_GERM_SENSITIVE)) return COMPONENT_INCOMPATIBLE src.decomp_flags = decomp_flags @@ -50,55 +51,30 @@ src.stink_particles = stink_particles - handle_movement() - /datum/component/decomposition/Destroy() . = ..() if(particle_effect) QDEL_NULL(particle_effect) /datum/component/decomposition/RegisterWithParent() - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(handle_movement)) - RegisterSignals(parent, list( - COMSIG_ITEM_PICKUP, //person picks up an item - COMSIG_ATOM_ENTERED), //Object enters a storage object (boxes, etc.) - PROC_REF(picked_up), - ) - RegisterSignals(parent, list( - COMSIG_ITEM_DROPPED, //Object is dropped anywhere - COMSIG_ATOM_EXITED), //Object exits a storage object (boxes, etc) - PROC_REF(dropped), - ) + RegisterSignal(parent, COMSIG_ATOM_GERM_EXPOSED, PROC_REF(start_timer)) + RegisterSignal(parent, COMSIG_ATOM_GERM_UNEXPOSED, PROC_REF(remove_timer)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine)) /datum/component/decomposition/UnregisterFromParent() UnregisterSignal(parent, list( - COMSIG_ITEM_PICKUP, - COMSIG_ATOM_ENTERED, - COMSIG_MOVABLE_MOVED, - COMSIG_ITEM_DROPPED, - COMSIG_ATOM_EXITED, - COMSIG_ATOM_EXAMINE)) + COMSIG_ATOM_GERM_EXPOSED, + COMSIG_ATOM_GERM_UNEXPOSED, + COMSIG_ATOM_EXAMINE + )) -/datum/component/decomposition/proc/handle_movement() +/datum/component/decomposition/proc/start_timer() SIGNAL_HANDLER if(!handled) // If maploaded, has someone touched this previously? + handled = TRUE // First germ exposure is ignored return - var/obj/food = parent // Doesn't HAVE to be food, that's just what it's intended for - - var/turf/open/open_turf = food.loc - - if(!istype(open_turf) || islava(open_turf) || isasteroidturf(open_turf)) //Are we actually in a valid open turf? - remove_timer() - return - - for(var/atom/movable/content as anything in open_turf.contents) - if(GLOB.typecache_elevated_structures[content.type]) - remove_timer() - return - // If all other checks fail, then begin decomposition. decomp_timerid = addtimer(CALLBACK(src, PROC_REF(decompose)), time_remaining, TIMER_STOPPABLE | TIMER_UNIQUE) @@ -130,17 +106,6 @@ deltimer(stink_timerid) stink_timerid = null -/datum/component/decomposition/proc/dropped() - SIGNAL_HANDLER - protected = FALSE - handle_movement() - -/datum/component/decomposition/proc/picked_up() - SIGNAL_HANDLER - remove_timer() - protected = TRUE - handled = TRUE - /datum/component/decomposition/proc/stink_up() stink_timerid = null // Neither should happen, but to be sure diff --git a/code/datums/components/food/germ_sensitive.dm b/code/datums/components/food/germ_sensitive.dm new file mode 100644 index 00000000000..7b362fb8182 --- /dev/null +++ b/code/datums/components/food/germ_sensitive.dm @@ -0,0 +1,124 @@ +// Don't eat off the floor or hold parent object with dirty hands, you'll get sick + +/// Time needed for bacteria to infect the parent object +#define GERM_EXPOSURE_DELAY (5 SECONDS) // Five-second rule + +/// Possible diseases +GLOBAL_LIST_INIT(floor_diseases, list( + /datum/disease/advance/nebula_nausea = 2, + /datum/disease/advance/gastritium = 2, + /datum/disease/advance/carpellosis = 1, +)) + +/// Makes items infective if left on floor, also sending corresponding signals to parent +/datum/component/germ_sensitive + /// Timer for counting delay before becoming infective + var/timer_id + /// Whether it is already infective + var/infective = FALSE + +/datum/component/germ_sensitive/Initialize(mapload) + if(!isobj(parent)) + return COMPONENT_INCOMPATIBLE + + ADD_TRAIT(parent, TRAIT_GERM_SENSITIVE, src) + + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(handle_movement)) + RegisterSignal(parent, COMSIG_ATOM_WASHED, PROC_REF(wash)) //Wash germs off dirty things + + RegisterSignals(parent, list( + COMSIG_ITEM_DROPPED, //Dropped into the world + COMSIG_ATOM_EXITED, //Object exits a storage object (tables, boxes, etc) + ), + PROC_REF(dropped)) + + RegisterSignals(parent, list( + COMSIG_ITEM_PICKUP, //Picked up by mob + COMSIG_ATOM_ENTERED, //Object enters a storage object (tables, boxes, etc.) + ), + PROC_REF(picked_up)) + + // Map spawned items are protected until moved + if(!mapload) + handle_movement() + +/datum/component/germ_sensitive/UnregisterFromParent() + REMOVE_TRAIT(parent, TRAIT_GERM_SENSITIVE, src) + UnregisterSignal(parent, list( + COMSIG_ATOM_EXAMINE, + COMSIG_MOVABLE_MOVED, + COMSIG_ATOM_WASHED, + COMSIG_ITEM_DROPPED, + COMSIG_ATOM_EXITED, + COMSIG_ITEM_PICKUP, + COMSIG_ATOM_ENTERED, + )) + +/datum/component/germ_sensitive/Destroy() + remove_timer() + return ..() + +/datum/component/germ_sensitive/proc/remove_timer() + if(!timer_id) + return + deltimer(timer_id) + timer_id = null + +/datum/component/germ_sensitive/proc/handle_movement() + SIGNAL_HANDLER + + var/obj/parent_object = parent + var/turf/open/open_turf = parent_object.loc + + // Is parent on valid open turf? + if(!istype(open_turf) || islava(open_turf) || isasteroidturf(open_turf) || !parent_object.has_gravity()) + remove_timer() + SEND_SIGNAL(parent, COMSIG_ATOM_GERM_UNEXPOSED, src) + return + + // Is parent on an elevated structure? + for(var/atom/movable/content as anything in open_turf.contents) + if(GLOB.typecache_elevated_structures[content.type]) + remove_timer() + SEND_SIGNAL(parent, COMSIG_ATOM_GERM_UNEXPOSED, src) + return + + // Exposed to bacteria, start countdown until becoming infected + timer_id = addtimer(CALLBACK(src, PROC_REF(expose_to_germs)), GERM_EXPOSURE_DELAY, TIMER_STOPPABLE | TIMER_UNIQUE) + +/datum/component/germ_sensitive/proc/picked_up() + SIGNAL_HANDLER + SEND_SIGNAL(parent, COMSIG_ATOM_GERM_UNEXPOSED, src) + remove_timer() + +/datum/component/germ_sensitive/proc/dropped() + SIGNAL_HANDLER + handle_movement() + +/datum/component/germ_sensitive/proc/examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + if(infective) + examine_list += span_warning("[parent] looks dirty and not safe to consume.") + +/datum/component/germ_sensitive/proc/expose_to_germs() + // Admin spawned items are never exposed + var/atom/parent_atom = parent + if(parent_atom.flags_1 & ADMIN_SPAWNED_1) + return + + SEND_SIGNAL(parent, COMSIG_ATOM_GERM_EXPOSED, src) + + if(infective) + return + infective = TRUE + + var/random_disease = pick_weight(GLOB.floor_diseases) + parent.AddComponent(/datum/component/infective, new random_disease, weak = TRUE) + +/datum/component/germ_sensitive/proc/wash() + if(infective) + infective = FALSE + qdel(parent.GetComponent(/datum/component/infective)) + +#undef GERM_EXPOSURE_DELAY diff --git a/code/datums/components/infective.dm b/code/datums/components/infective.dm index 65afb2617c3..71d5a6ace5c 100644 --- a/code/datums/components/infective.dm +++ b/code/datums/components/infective.dm @@ -2,9 +2,13 @@ var/list/datum/disease/diseases //make sure these are the static, non-processing versions! var/expire_time var/required_clean_types = CLEAN_TYPE_DISEASE + /// The infection is weak and can only infect on consumption with small chance + var/is_weak = FALSE + /// Chance of weak infection on consumption + var/weak_infection_chance = 10 -/datum/component/infective/Initialize(list/datum/disease/_diseases, expire_in) +/datum/component/infective/Initialize(list/datum/disease/_diseases, expire_in, weak = FALSE) if(islist(_diseases)) diseases = _diseases else @@ -16,28 +20,40 @@ if(!ismovable(parent)) return COMPONENT_INCOMPATIBLE - var/static/list/disease_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(try_infect_crossed), - ) - AddComponent(/datum/component/connect_loc_behalf, parent, disease_connections) + is_weak = weak - RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean)) - RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, PROC_REF(try_infect_buckle)) - RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(try_infect_collide)) - RegisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(try_infect_impact_zone)) - if(isitem(parent)) - RegisterSignal(parent, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(try_infect_attack_zone)) - RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(try_infect_attack)) - RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(try_infect_equipped)) + if(is_weak && isitem(parent)) RegisterSignal(parent, COMSIG_FOOD_EATEN, PROC_REF(try_infect_eat)) - if(istype(parent, /obj/item/reagent_containers/cup)) - RegisterSignal(parent, COMSIG_GLASS_DRANK, PROC_REF(try_infect_drink)) - else if(istype(parent, /obj/effect/decal/cleanable/blood/gibs)) - RegisterSignal(parent, COMSIG_GIBS_STREAK, PROC_REF(try_infect_streak)) + RegisterSignal(parent, COMSIG_PILL_CONSUMED, PROC_REF(try_infect_eat)) + else + var/static/list/disease_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(try_infect_crossed), + ) + AddComponent(/datum/component/connect_loc_behalf, parent, disease_connections) + + RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean)) + RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, PROC_REF(try_infect_buckle)) + RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(try_infect_collide)) + RegisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(try_infect_impact_zone)) + if(isitem(parent)) + RegisterSignal(parent, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(try_infect_attack_zone)) + RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(try_infect_attack)) + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(try_infect_equipped)) + RegisterSignal(parent, COMSIG_FOOD_EATEN, PROC_REF(try_infect_eat)) + RegisterSignal(parent, COMSIG_PILL_CONSUMED, PROC_REF(try_infect_eat)) + if(istype(parent, /obj/item/reagent_containers/cup)) + RegisterSignal(parent, COMSIG_GLASS_DRANK, PROC_REF(try_infect_drink)) + else if(istype(parent, /obj/effect/decal/cleanable/blood/gibs)) + RegisterSignal(parent, COMSIG_GIBS_STREAK, PROC_REF(try_infect_streak)) /datum/component/infective/proc/try_infect_eat(datum/source, mob/living/eater, mob/living/feeder) SIGNAL_HANDLER + eater.add_mood_event("disgust", /datum/mood_event/disgust/dirty_food) + + if(is_weak && !prob(weak_infection_chance)) + return + for(var/V in diseases) eater.ForceContractDisease(V) try_infect(feeder, BODY_ZONE_L_ARM) diff --git a/code/datums/diseases/advance/floor_diseases/carpellosis.dm b/code/datums/diseases/advance/floor_diseases/carpellosis.dm new file mode 100644 index 00000000000..12685a44c47 --- /dev/null +++ b/code/datums/diseases/advance/floor_diseases/carpellosis.dm @@ -0,0 +1,104 @@ +#define GNASHING_RANGE 7 + +/// Caused by dirty food. Makes you growl at people and bite them spontaneously. +/datum/disease/advance/carpellosis + name = "Carpellosis" + desc = "You have an angry space carp inside." + form = "Parasite" + agent = "Carp Ella" + cures = list(/datum/reagent/carpet) + viable_mobtypes = list(/mob/living/carbon/human) + required_organs = list(/obj/item/organ/internal/stomach) + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + severity = DISEASE_SEVERITY_MEDIUM + max_stages = 5 + /// The chance of Carp Ella to spawn on cure + var/ella_spawn_chance = 10 + /// Whether the max stage was achieved in disease lifecycle + var/max_stage_reached = FALSE + /// Carp ability gained on max stage + var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/rift_ability + /// Whether the host has carp ability + var/ability_granted = FALSE + +/datum/disease/advance/carpellosis/New() + symptoms = list(new/datum/symptom/headache) + ..() + +/datum/disease/advance/carpellosis/generate_cure() + cures = list(pick(cures)) + var/datum/reagent/cure = GLOB.chemical_reagents_list[cures[1]] + cure_text = cure.name + +/datum/disease/advance/carpellosis/stage_act(seconds_per_tick, times_fired) + . = ..() + if(!.) + return + + + switch(stage) + if(2) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("You want to wag your tail...")) + affected_mob.emote("wag") + if(3) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("You suddenly feel like swimming in space...")) + else if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + affected_mob.visible_message("gnashes.", visible_message_flags = EMOTE_MESSAGE) + if(4) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + gnash_someone() + else if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + affected_mob.visible_message("gnashes.", visible_message_flags = EMOTE_MESSAGE) + if(5) + max_stage_reached = TRUE + grant_ability() + if(SPT_PROB(2, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + gnash_someone() + else if(SPT_PROB(2, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + affected_mob.visible_message("gnashes.", visible_message_flags = EMOTE_MESSAGE) + +/datum/disease/advance/carpellosis/Destroy() + if(ability_granted) + QDEL_NULL(rift_ability) + return ..() + +/datum/disease/advance/carpellosis/cure() + if(ability_granted) + rift_ability.Remove(affected_mob) + if(max_stage_reached && prob(ella_spawn_chance)) + to_chat(affected_mob, span_warning("Something comes out of you!")) + new /mob/living/basic/carp/ella(affected_mob.loc) + return ..() + +/datum/disease/advance/carpellosis/proc/grant_ability() + if(ability_granted) + return + rift_ability = new(src) + rift_ability.Grant(affected_mob) + rift_ability.HideFrom(affected_mob) + ability_granted = TRUE + +/datum/disease/advance/carpellosis/proc/find_nearby_human() + var/list/surroundings = orange(GNASHING_RANGE, affected_mob) + for(var/mob/human as anything in typecache_filter_list(surroundings, typecacheof(/mob/living/carbon/human))) + if(human.stat != DEAD && !(HAS_TRAIT(human, TRAIT_FAKEDEATH))) + return human + +/datum/disease/advance/carpellosis/proc/gnash_someone() + var/mob/living/carbon/human/target = find_nearby_human() + if(isnull(target) || !target.get_bodypart(BODY_ZONE_HEAD)) // Need mouth to gnash + to_chat(affected_mob, span_warning("You want to gnash at someone...")) + return + to_chat(affected_mob, span_warning("[target.name] makes you angry for some reason...")) + if(ability_granted && !affected_mob.Adjacent(target)) + rift_ability.Trigger(target = target) + affected_mob.face_atom(target) + if(affected_mob.Adjacent(target)) + affected_mob.set_combat_mode(TRUE) + target.attack_paw(affected_mob) + else + affected_mob.visible_message("gnashes at [target.name].", visible_message_flags = EMOTE_MESSAGE) + +#undef GNASHING_RANGE diff --git a/code/datums/diseases/advance/floor_diseases/gastritium.dm b/code/datums/diseases/advance/floor_diseases/gastritium.dm new file mode 100644 index 00000000000..59b619c6fb1 --- /dev/null +++ b/code/datums/diseases/advance/floor_diseases/gastritium.dm @@ -0,0 +1,60 @@ +/// Caused by dirty food. Makes you burp out Tritium, sometimes burning hot! +/datum/disease/advance/gastritium + name = "Gastritium" + desc = "If left untreated, may manifest in severe Tritium heartburn." + form = "Infection" + agent = "Atmobacter Polyri" + cures = list(/datum/reagent/firefighting_foam) + viable_mobtypes = list(/mob/living/carbon/human) + required_organs = list(/obj/item/organ/internal/stomach) + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + severity = DISEASE_SEVERITY_HARMFUL + max_stages = 5 + /// The chance of burped out tritium to be hot during max stage + var/tritium_burp_hot_chance = 10 + +/datum/disease/advance/gastritium/New() + symptoms = list(new/datum/symptom/fever) + ..() + +/datum/disease/advance/gastritium/generate_cure() + cures = list(pick(cures)) + var/datum/reagent/cure = GLOB.chemical_reagents_list[cures[1]] + cure_text = cure.name + +/datum/disease/advance/gastritium/stage_act(seconds_per_tick, times_fired) + . = ..() + if(!.) + return + + switch(stage) + if(2) + if(SPT_PROB(1, seconds_per_tick)) + affected_mob.emote("burp") + if(3) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("Your stomach makes turbine noises...")) + else if(SPT_PROB(1, seconds_per_tick)) + affected_mob.emote("burp") + if(4) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("You're starting to feel like a burn chamber...")) + else if(SPT_PROB(1, seconds_per_tick)) + tritium_burp() + if(5) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("You feel like you're about to delam...")) + else if(SPT_PROB(1, seconds_per_tick)) + tritium_burp(hot_chance = TRUE) + +/datum/disease/advance/gastritium/proc/tritium_burp(hot_chance = FALSE) + var/datum/gas_mixture/burp = new + ADD_GAS(/datum/gas/tritium, burp.gases) + burp.gases[/datum/gas/tritium][MOLES] = MOLES_GAS_VISIBLE + burp.temperature = affected_mob.bodytemperature + if(hot_chance && prob(tritium_burp_hot_chance)) + burp.temperature = TRITIUM_MINIMUM_BURN_TEMPERATURE + if(affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("Your throat feels hot!")) + affected_mob.visible_message("burps out green gas.", visible_message_flags = EMOTE_MESSAGE) + affected_mob.loc.assume_air(burp) diff --git a/code/datums/diseases/advance/floor_diseases/nebula_nausea.dm b/code/datums/diseases/advance/floor_diseases/nebula_nausea.dm new file mode 100644 index 00000000000..6ffc127ba63 --- /dev/null +++ b/code/datums/diseases/advance/floor_diseases/nebula_nausea.dm @@ -0,0 +1,40 @@ +/// Caused by dirty food. Makes you vomit stars. +/datum/disease/advance/nebula_nausea + name = "Nebula Nausea" + desc = "You can't contain the colorful beauty of the cosmos inside." + form = "Condition" + agent = "Stars" + cures = list(/datum/reagent/bluespace) + viable_mobtypes = list(/mob/living/carbon/human) + required_organs = list(/obj/item/organ/internal/stomach) + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + severity = DISEASE_SEVERITY_MEDIUM + max_stages = 5 + +/datum/disease/advance/nebula_nausea/New() + symptoms = list(new/datum/symptom/vomit/nebula) + ..() + +/datum/disease/advance/nebula_nausea/generate_cure() + cures = list(pick(cures)) + var/datum/reagent/cure = GLOB.chemical_reagents_list[cures[1]] + cure_text = cure.name + +/datum/disease/advance/nebula_nausea/stage_act(seconds_per_tick, times_fired) + . = ..() + if(!.) + return + + switch(stage) + if(2) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("The colorful beauty of the cosmos seems to have taken a toll on your equilibrium.")) + if(3) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("Your stomach swirls with colors unseen by human eyes.")) + if(4) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("It feels like you're floating through a maelstrom of celestial colors.")) + if(5) + if(SPT_PROB(1, seconds_per_tick) && affected_mob.stat == CONSCIOUS) + to_chat(affected_mob, span_warning("Your stomach has become a turbulent nebula, swirling with kaleidoscopic patterns.")) diff --git a/code/datums/diseases/advance/symptoms/vomit.dm b/code/datums/diseases/advance/symptoms/vomit.dm index 92bca8cfbf5..5382a4e4e81 100644 --- a/code/datums/diseases/advance/symptoms/vomit.dm +++ b/code/datums/diseases/advance/symptoms/vomit.dm @@ -20,6 +20,7 @@ and your disease can spread via people walking on vomit. base_message_chance = 100 symptom_delay_min = 25 symptom_delay_max = 80 + var/vomit_nebula = FALSE var/vomit_blood = FALSE var/proj_vomit = 0 threshold_descs = list( @@ -52,4 +53,13 @@ and your disease can spread via people walking on vomit. vomit(M) /datum/symptom/vomit/proc/vomit(mob/living/carbon/M) - M.vomit(20, vomit_blood, distance = proj_vomit) + if(vomit_nebula) + M.vomit(lost_nutrition = 10, blood = vomit_blood, vomit_type = VOMIT_NEBULA, stun = FALSE, distance = proj_vomit) + else + M.vomit(lost_nutrition = 20, blood = vomit_blood, distance = proj_vomit) + +/datum/symptom/vomit/nebula + name = "Nebula Vomiting" + desc = "The condition irritates the stomach, causing occasional vomit with stars that does not stun." + illness = "Nebula Nausea" + vomit_nebula = TRUE diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index 1c00825e2d1..01281963cc1 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -61,6 +61,11 @@ description = "The stench of rotting carcasses is unbearable!" mood_change = -12 +/datum/mood_event/disgust/dirty_food + description = "It was too dirty to eat..." + mood_change = -6 + timeout = 4 MINUTES + //Generic needs events /datum/mood_event/favorite_food description = "I really enjoyed eating that." diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 19853660361..c6e8093bb36 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -174,6 +174,21 @@ reagents.trans_to(H, reagents.total_volume, transfered_by = user, methods = INGEST) qdel(src) +/obj/effect/decal/cleanable/vomit/nebula + name = "nebula vomit" + desc = "Gosh, how... beautiful." + icon_state = "vomitnebula_1" + random_icon_states = list("vomitnebula_1", "vomitnebula_2", "vomitnebula_3", "vomitnebula_4") + beauty = 10 + +/obj/effect/decal/cleanable/vomit/nebula/Initialize(mapload, list/datum/disease/diseases) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/effect/decal/cleanable/vomit/nebula/update_overlays() + . = ..() + . += emissive_appearance(icon, icon_state, src, alpha = src.alpha) + /obj/effect/decal/cleanable/vomit/old name = "crusty dried vomit" desc = "You try not to look at the chunks, and fail." diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index d92b930cd6d..556c11b3153 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1291,6 +1291,8 @@ /obj/item/wash(clean_types) . = ..() + SEND_SIGNAL(src, COMSIG_ATOM_WASHED) + if(ismob(loc)) var/mob/mob_loc = loc mob_loc.regenerate_icons() diff --git a/code/game/objects/items/food/_food.dm b/code/game/objects/items/food/_food.dm index b9d9551bfb9..2cca10d8df9 100644 --- a/code/game/objects/items/food/_food.dm +++ b/code/game/objects/items/food/_food.dm @@ -63,7 +63,7 @@ make_processable() make_leave_trash() make_grillable() - make_decompose(mapload) + make_germ_sensitive(mapload) make_bakeable() make_microwaveable() ADD_TRAIT(src, FISHING_BAIT_TRAIT, INNATE_TRAIT) @@ -110,8 +110,13 @@ AddElement(/datum/element/food_trash, trash_type) return -///This proc makes things decompose. Set preserved_food to TRUE to make it never decompose. +///This proc makes things infective and decomposing when they stay on the floor for too long. +///Set preserved_food to TRUE to make it never decompose. ///Set decomp_req_handle to TRUE to only make it decompose when someone picks it up. -/obj/item/food/proc/make_decompose(mapload) +///Requires /datum/component/germ_sensitive to detect exposure +/obj/item/food/proc/make_germ_sensitive(mapload) + if(istype(src, /obj/item/food/bowled) || istype(src, /obj/item/food/canned) || !isnull(trash_type)) + return // You don't eat the package and it protects from decomposing + AddComponent(/datum/component/germ_sensitive, mapload) if(!preserved_food) AddComponent(/datum/component/decomposition, mapload, decomp_req_handle, decomp_flags = foodtypes, decomp_result = decomp_type, ant_attracting = ant_attracting, custom_time = decomposition_time, stink_particles = decomposition_particles) diff --git a/code/game/objects/items/food/packaged.dm b/code/game/objects/items/food/packaged.dm index ce8a6f6a825..515c02f4706 100644 --- a/code/game/objects/items/food/packaged.dm +++ b/code/game/objects/items/food/packaged.dm @@ -21,7 +21,6 @@ playsound(user.loc, 'sound/items/foodcanopen.ogg', 50) reagents.flags |= OPENCONTAINER preserved_food = FALSE - make_decompose() /obj/item/food/canned/attack_self(mob/user) if(!is_drainable()) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 9a41ca207a2..99f079023bc 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -638,22 +638,26 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/AllowDrop() return TRUE -/turf/proc/add_vomit_floor(mob/living/M, toxvomit = NONE, purge_ratio = 0.1) +/turf/proc/add_vomit_floor(mob/living/M, vomit_type = VOMIT_TOXIC, purge_ratio = 0.1) - var/obj/effect/decal/cleanable/vomit/V = new /obj/effect/decal/cleanable/vomit(src, M.get_static_viruses()) + var/obj/effect/decal/cleanable/vomit/vomit + if (vomit_type == VOMIT_NEBULA) + vomit = new /obj/effect/decal/cleanable/vomit/nebula(src, M.get_static_viruses()) + else + vomit = new /obj/effect/decal/cleanable/vomit(src, M.get_static_viruses()) //if the vomit combined, apply toxicity and reagents to the old vomit - if (QDELETED(V)) - V = locate() in src - if(!V) + if (QDELETED(vomit)) + vomit = locate() in src + if(!vomit) return // Apply the proper icon set based on vomit type - if(toxvomit == VOMIT_PURPLE) - V.icon_state = "vomitpurp_[pick(1,4)]" - else if (toxvomit == VOMIT_TOXIC) - V.icon_state = "vomittox_[pick(1,4)]" + if(vomit_type == VOMIT_PURPLE) + vomit.icon_state = "vomitpurp_[pick(1,4)]" + else if (vomit_type == VOMIT_TOXIC) + vomit.icon_state = "vomittox_[pick(1,4)]" if (purge_ratio && iscarbon(M)) - clear_reagents_to_vomit_pool(M, V, purge_ratio) + clear_reagents_to_vomit_pool(M, vomit, purge_ratio) /proc/clear_reagents_to_vomit_pool(mob/living/carbon/M, obj/effect/decal/cleanable/vomit/V, purge_ratio = 0.1) var/obj/item/organ/internal/stomach/belly = M.get_organ_slot(ORGAN_SLOT_STOMACH) diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index f4c92a1db52..f356be9b1c6 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -206,7 +206,7 @@ ///List of plants all harvested from the same batch. var/list/result = list() ///Tile of the harvester to deposit the growables. - var/output_loc = parent.Adjacent(user) ? user.loc : parent.loc //needed for TK + var/output_loc = parent.loc ///Name of the grown products. var/product_name ///The Number of products produced by the plant, typically the yield. Modified by certain traits. diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 04e754c8d22..6bd20d9a7e7 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -202,7 +202,7 @@ //Puts the item our active hand if possible. Failing that it tries other hands. Returns TRUE on success. -//If both fail it drops it on the floor and returns FALSE. +//If both fail it drops it on the floor (or nearby tables if germ sensitive) and returns FALSE. //This is probably the main one you need to know :) /mob/proc/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, forced = FALSE, ignore_animation = TRUE) if(QDELETED(I)) @@ -240,7 +240,45 @@ if(del_on_fail) qdel(I) return FALSE + + // Failed to put in hands - drop the item var/atom/location = drop_location() + + // Try dropping on nearby tables if germ sensitive (except table behind you) + if(HAS_TRAIT(I, TRAIT_GERM_SENSITIVE)) + var/list/dirs = list( // All dirs in clockwise order + NORTH, + NORTHEAST, + EAST, + SOUTHEAST, + SOUTH, + SOUTHWEST, + WEST, + NORTHWEST, + ) + var/dir_count = dirs.len + var/facing_dir_index = dirs.Find(dir) + var/cw_index = facing_dir_index + var/ccw_index = facing_dir_index + var/list/turfs_ordered = list(get_step(src, dir)) + + // Build ordered list of turfs starting from the front facing + for(var/i in 1 to ROUND_UP(dir_count/2) - 1) + cw_index++ + if(cw_index > dir_count) + cw_index = 1 + turfs_ordered += get_step(src, dirs[cw_index]) // Add next tile on your right + ccw_index-- + if(ccw_index <= 0) + ccw_index = dir_count + turfs_ordered += get_step(src, dirs[ccw_index]) // Add next tile on your left + + // Check tables on these turfs + for(var/turf in turfs_ordered) + if(locate(/obj/structure/table) in turf) + location = turf + break + I.forceMove(location) I.layer = initial(I.layer) SET_PLANE_EXPLICIT(I, initial(I.plane), location) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index 93174f7970a..29a444707cd 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -273,6 +273,17 @@ #undef RARE_CAYENNE_CHANCE +///Carp-parasite from carpellosis disease +/mob/living/basic/carp/ella + name = "Ella" + real_name = "Ella" + desc = "It came out of someone." + gold_core_spawnable = NO_SPAWN + +/mob/living/basic/carp/ella/Initialize(mapload) + . = ..() + death() // It comes into the world dead when the disease is cured + ///Wild carp that just vibe ya know /mob/living/basic/carp/passive name = "passive carp" diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm index ead552c95a2..9ad9fb52572 100644 --- a/code/modules/reagents/reagent_containers/pill.dm +++ b/code/modules/reagents/reagent_containers/pill.dm @@ -23,6 +23,8 @@ if(reagents.total_volume && rename_with_volume) name += " ([reagents.total_volume]u)" + AddComponent(/datum/component/germ_sensitive, mapload) + /obj/item/reagent_containers/pill/attack(mob/M, mob/user, def_zone) if(!canconsume(M, user)) return FALSE @@ -48,7 +50,8 @@ /obj/item/reagent_containers/pill/proc/on_consumption(mob/M, mob/user) if(icon_state == "pill4" && prob(5)) //you take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), M, span_notice("[pick(strings(REDPILL_FILE, "redpill_questions"))]")), 50) - + if(apply_type == INGEST) + SEND_SIGNAL(src, COMSIG_PILL_CONSUMED, eater = M, feeder = user) if(reagents.total_volume) reagents.trans_to(M, reagents.total_volume, transfered_by = user, methods = apply_type) qdel(src) diff --git a/code/modules/unit_tests/hydroponics_harvest.dm b/code/modules/unit_tests/hydroponics_harvest.dm index 1e6d39a7329..c384b3790a6 100644 --- a/code/modules/unit_tests/hydroponics_harvest.dm +++ b/code/modules/unit_tests/hydroponics_harvest.dm @@ -28,7 +28,14 @@ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent) hydroponics_tray.forceMove(run_loc_floor_bottom_left) - human.forceMove(locate((run_loc_floor_bottom_left.x + 1), run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) + + var/nearby_loc = locate((run_loc_floor_bottom_left.x + 1), run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z) + human.forceMove(nearby_loc) + + var/seeds_loc = locate((run_loc_floor_bottom_left.x + 1), (run_loc_floor_bottom_left.y + 1), run_loc_floor_bottom_left.z) + planted_food_seed.forceMove(seeds_loc) + planted_not_food_seed.forceMove(seeds_loc) + planted_densified_seed.forceMove(seeds_loc) // Apples should harvest 10 apples with 10u nutrients and 4u vitamins. test_seed(hydroponics_tray, planted_food_seed, human) @@ -70,7 +77,7 @@ tray.attack_hand(user) var/list/obj/item/all_harvested_items = list() - for(var/obj/item/harvested_food in user.drop_location()) + for(var/obj/item/harvested_food in tray.drop_location()) all_harvested_items += harvested_food if(!all_harvested_items.len) diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi index 36f30f74f1f..93f836f4c84 100644 Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ diff --git a/tgstation.dme b/tgstation.dme index abde5e164f8..f578c750ad6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1075,6 +1075,7 @@ #include "code\datums\components\fantasy\suffixes.dm" #include "code\datums\components\food\decomposition.dm" #include "code\datums\components\food\edible.dm" +#include "code\datums\components\food\germ_sensitive.dm" #include "code\datums\components\food\golem_food.dm" #include "code\datums\components\food\ice_cream_holder.dm" #include "code\datums\components\pet_commands\fetch.dm" @@ -1121,6 +1122,9 @@ #include "code\datums\diseases\wizarditis.dm" #include "code\datums\diseases\advance\advance.dm" #include "code\datums\diseases\advance\presets.dm" +#include "code\datums\diseases\advance\floor_diseases\carpellosis.dm" +#include "code\datums\diseases\advance\floor_diseases\gastritium.dm" +#include "code\datums\diseases\advance\floor_diseases\nebula_nausea.dm" #include "code\datums\diseases\advance\symptoms\beard.dm" #include "code\datums\diseases\advance\symptoms\chills.dm" #include "code\datums\diseases\advance\symptoms\choking.dm"