diff --git a/code/__DEFINES/~~bubber_defines/signals.dm b/code/__DEFINES/~~bubber_defines/signals.dm index b231b2e46e5..b316f85569e 100644 --- a/code/__DEFINES/~~bubber_defines/signals.dm +++ b/code/__DEFINES/~~bubber_defines/signals.dm @@ -49,3 +49,8 @@ /// returns the bitflag if it indeed reached max blood, otherwise NONE #define COMSIG_MOB_REACHED_MAX_BLOOD "mob_reached_max_blood" #define REACHED_MAX_BLOOD (1 << 0) + +/// From /obj/structure/trash_pile/attack_hand - trigged on successful search. (atom/source, obj/structure/trash_pile) +#define COMSIG_LIVING_SEARCHED_TRASH_PILE "living_searched_trash_pile" +/// From /datum/component/cleaner/proc/clean - triggered on successful clean. (/datum/component/cleaner/source, mob/living/user) +#define COMSIG_ATOM_POST_CLEAN "atom_post_clean" diff --git a/code/__DEFINES/~~bubber_defines/traits/declarations.dm b/code/__DEFINES/~~bubber_defines/traits/declarations.dm index 091cb52f9c8..594783f8609 100644 --- a/code/__DEFINES/~~bubber_defines/traits/declarations.dm +++ b/code/__DEFINES/~~bubber_defines/traits/declarations.dm @@ -5,6 +5,7 @@ #define TRAIT_CHANGELING_ZOMBIE "changelingzombie" #define TRAIT_RESEARCH_CYBORG "research_cyborg" #define TRAIT_BILINGUAL "Bilingual" +#define TRAIT_DIRTY "dirty" /// Cyborgs with unique sprites /// 32x32 quadruped skins with resting, sitting, and belly up sprites diff --git a/code/datums/components/cleaner.dm b/code/datums/components/cleaner.dm index e7efd2fc52c..014006842a2 100644 --- a/code/datums/components/cleaner.dm +++ b/code/datums/components/cleaner.dm @@ -127,6 +127,11 @@ //do the cleaning var/clean_succeeded = FALSE if(do_after(user, cleaning_duration, target = target)) + + // BUBBER EDIT ADDITION BEGIN - Dirty quirk + SEND_SIGNAL(target, COMSIG_ATOM_POST_CLEAN, user) + // BUBBER EDIT ADDITION END + clean_succeeded = TRUE for(var/obj/effect/decal/cleanable/cleanable_decal in target) //it's important to do this before you wash all of the cleanables off if(call_wash && grant_xp) diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm index 1d0c689bff4..f8e8e1edcdf 100644 --- a/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm @@ -81,6 +81,13 @@ if(LAZYACCESS(ignore_list, wash_potential)) continue + + // BUBBER EDIT ADDITION BEGIN - Dirty quirk + if (HAS_TRAIT(wash_potential, TRAIT_DIRTY)) + found_target = wash_potential + break + // BUGGER EDIT ADDITION END + if(our_access_flags & BOT_COVER_EMAGGED) controller.add_to_blacklist(wash_potential) found_target = wash_potential diff --git a/modular_zubbers/code/datums/quirks/negative_quirks/dirty.dm b/modular_zubbers/code/datums/quirks/negative_quirks/dirty.dm new file mode 100644 index 00000000000..cf27ed3813b --- /dev/null +++ b/modular_zubbers/code/datums/quirks/negative_quirks/dirty.dm @@ -0,0 +1,230 @@ +#define DIRTY_TILE_CHANCE 0.25 +#define REDIRTY_CHANCE 30 + +/datum/quirk/dirty + name = "Dirty" + desc = "You haven't taken a shower in a loooong time, and as a result, you are caked in an everpresent layer of grime and track it everywhere." + icon = FA_ICON_TRASH_ALT + value = -2 + mob_trait = TRAIT_DIRTY + quirk_flags = QUIRK_PROCESSES + gain_text = span_danger("You feel dirty!") + lose_text = span_notice("You're finally clean...") + medical_record_text = "Patient has atrocious hygiene standards." + hardcore_value = 2 + mail_goodies = list( + /obj/item/soap + ) + /// The text displayed on examine, set in preferences. + var/dirty_text + /// The hexcolor that all dirt graphics will use. + var/dirt_color + /// Next time we're hit, will a bunch of dust fly off us? + var/cloud_charged = TRUE + + /// Have we been cleaned, thereby disabling our dirt mechanics? + var/cleaned = FALSE + +/datum/quirk_constant_data/dirty + associated_typepath = /datum/quirk/dirty + customization_options = list( + /datum/preference/text/dirty_text, + /datum/preference/color/dirty_color, + ) + +/datum/preference/text/dirty_text + category = PREFERENCE_CATEGORY_MANUALLY_RENDERED + savefile_key = "dirty_quirk_text" + savefile_identifier = PREFERENCE_CHARACTER + can_randomize = FALSE + maximum_value_length = 100 + +/datum/preference/text/dirty_text/serialize(input) + return htmlrendertext(input) + +/datum/preference/text/dirty_text/deserialize(input, datum/preferences/preferences) + return htmlrendertext(input) + +/datum/preference/text/dirty_text/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences) + return + +/datum/preference/text/dirty_text/create_default_value() + return "They are positively covered in filth, and are tracking it everywhere!" + +/datum/preference/color/dirty_color + category = PREFERENCE_CATEGORY_MANUALLY_RENDERED + savefile_key = "dirty_quirk_color" + savefile_identifier = PREFERENCE_CHARACTER + can_randomize = FALSE + +/datum/preference/color/dirty_color/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences) + return + +/datum/preference/color/dirty_color/create_default_value() + return "#000000" + +/datum/quirk/dirty/process(seconds_per_tick) + if (cleaned) + return + for (var/mob/living/iter_mob in get_hearers_in_view(4, quirk_holder)) + if (HAS_TRAIT(iter_mob, TRAIT_DIRTY) || HAS_TRAIT(iter_mob, TRAIT_ANOSMIA)) + continue + iter_mob.add_mood_event("dirty_smell", /datum/mood_event/dirty_smell) + +/datum/quirk/dirty/add(client/client_source) + RegisterSignal(quirk_holder, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignals(quirk_holder, list(COMSIG_ATOM_POST_CLEAN, COMSIG_COMPONENT_CLEAN_ACT), PROC_REF(on_cleaned)) + RegisterSignal(quirk_holder, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + RegisterSignal(quirk_holder, COMSIG_LIVING_SEARCHED_TRASH_PILE, PROC_REF(searched_trash_pile)) + RegisterSignal(quirk_holder, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(on_check_block)) + +/datum/quirk/dirty/post_add() + dirty_text = quirk_holder.client.prefs.read_preference(/datum/preference/text/dirty_text) + dirt_color = quirk_holder.client.prefs.read_preference(/datum/preference/color/dirty_color) + + to_chat(quirk_holder, span_notice("You're dirty! You'll spread dirt when you walk, and can add a mood debuff to people around you. \ + Being cleaned will remove your filth - rummage through a trash pile to get it back.")) + + return ..() + +/datum/quirk/dirty/proc/searched_trash_pile(atom/signal_source, obj/structure/trash_pile/trash) + SIGNAL_HANDLER + + if (!cloud_charged) + to_chat(quirk_holder, span_notice("You find yourself covered in a thick layer of grime - a solid impact might knock it off in a cloud.")) + cloud_charged = TRUE + + if (!cleaned) + return + + if (!prob(REDIRTY_CHANCE)) + to_chat(quirk_holder, span_warning("You didn't manage to scrounge enough filth to right this wrong. Try again.")) + return + + set_cleaned(FALSE) + +/obj/effect/decal/cleanable/blood/footprints/dirty + name = "dirty footprints" + desc = "Footprints caked in grime. They smell awful." + dried = TRUE + bloodiness = 0 + +/datum/quirk/dirty/proc/on_moved(atom/movable/mover, atom/old_loc, dir, forced, list/old_locs) + SIGNAL_HANDLER + + if (cleaned) + return + if (quirk_holder.body_position == LYING_DOWN || (quirk_holder.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) + return + + var/turf/new_turf = get_turf(quirk_holder) + + if (prob(DIRTY_TILE_CHANCE)) + quirk_holder.balloon_alert_to_viewers("spreads dirt...") + new_turf.spawn_unique_cleanable(/obj/effect/decal/cleanable/dirt) + + var/mob/living/carbon/human/human_holder = quirk_holder + if (!istype(human_holder)) + return + + if (!isnull(human_holder.shoes)) + return + + var/obj/effect/decal/cleanable/blood/footprints/dirty/old_loc_prints = locate() in new_turf + + if (old_loc_prints) + return + + var/obj/effect/decal/cleanable/blood/footprints/dirty/new_loc_prints = new /obj/effect/decal/cleanable/blood/footprints/dirty(new_turf, null, null) + + // Find any leg of our human and add that to the footprint, instead of the default which is to just add the human type + for(var/obj/item/bodypart/leg/affecting in human_holder.bodyparts) + if(!affecting.bodypart_disabled) + LAZYSET(new_loc_prints.species_types, affecting.limb_id, TRUE) + + new_loc_prints.color = dirt_color + new_loc_prints.entered_dirs |= quirk_holder.dir + new_loc_prints.exited_dirs |= quirk_holder.dir + new_loc_prints.update_appearance() + + new_loc_prints.fade_into_nothing() + +/datum/effect_system/fluid_spread/smoke/dirty + var/color + +/datum/effect_system/fluid_spread/smoke/dirty/start(log) + var/start_loc = holder ? get_turf(holder) : src.location + var/obj/effect/particle_effect/fluid/smoke/smoke = new /obj/effect/particle_effect/fluid/smoke(start_loc, new /datum/fluid_group(amount)) + smoke.add_atom_colour(color, FIXED_COLOUR_PRIORITY) + smoke.spread() // Making the smoke spread immediately. + +/datum/quirk/dirty/proc/on_check_block(atom/hit_by, damage, attack_text, attack_type, armour_penetration, damage_type) + SIGNAL_HANDLER + + if (!cloud_charged || cleaned) + return + quirk_holder.visible_message( + span_warning("The impact knocks a cloud of grime and dust off [quirk_holder]!"), + span_notice("The impact knocks a cloud of grime and dust off you!") + ) + + var/datum/effect_system/fluid_spread/smoke/dirty/smoke = new /datum/effect_system/fluid_spread/smoke/dirty() + smoke.color = dirt_color + smoke.set_up(0, holder = quirk_holder, location = get_turf(quirk_holder)) + smoke.start() + + cloud_charged = FALSE + +/datum/quirk/dirty/proc/on_examine(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + if (cleaned) + return + if(!istype(user)) + return + + examine_list += span_warning(dirty_text) + +/// Setter proc for [cleaned]. Handles messages and other reactions. +/datum/quirk/dirty/proc/on_cleaned(datum/source) + SIGNAL_HANDLER + + INVOKE_ASYNC(quirk_holder, TYPE_PROC_REF(/mob, emote), "scream") + + set_cleaned(TRUE) + +/datum/quirk/dirty/proc/set_cleaned(new_cleaned) + + if (cleaned && !new_cleaned) + quirk_holder.visible_message( + span_warning("[quirk_holder] is once again covered in filth, [quirk_holder.p_their()] hygiene forever accursed."), + span_notice("Your find yourself lathered in filth once again. A sense of relief washes over you.") + ) + quirk_holder.clear_mood_event("dirty_quirk_washed") + ADD_TRAIT(quirk_holder, TRAIT_DIRTY, QUIRK_TRAIT) + else if (!cleaned && new_cleaned) + quirk_holder.visible_message( + span_warning("[quirk_holder] squirms and writhes, fighting against the cleansing!"), + span_userdanger("No! NO! Your filth! It's gone!") + ) + quirk_holder.add_mood_event("dirty_quirk_washed", /datum/mood_event/dirty_washed) + REMOVE_TRAIT(quirk_holder, TRAIT_DIRTY, QUIRK_TRAIT) + + cleaned = new_cleaned + +/datum/mood_event/dirty_smell + description = "Something... or someone... smells rotten around here..." + mood_change = -2 + timeout = 25 SECONDS + +/datum/mood_event/dirty_smell/add_effects(...) + . = ..() + + to_chat(owner, span_warning("You catch the distinct scent of someone who hasn't showered in years. Disgusting.")) + +/datum/mood_event/dirty_washed + description = "What is this smell...? Lavender soap? A hint of citrus?! ELDERBERRIES?! DISGUSTING! I need to wash myself in dirt!" + mood_change = -6 + +#undef DIRTY_TILE_CHANCE +#undef REDIRTY_CHANCE diff --git a/modular_zubbers/code/game/objects/structures/trash_pile.dm b/modular_zubbers/code/game/objects/structures/trash_pile.dm index 2d3b2ed760e..9f1200d7227 100644 --- a/modular_zubbers/code/game/objects/structures/trash_pile.dm +++ b/modular_zubbers/code/game/objects/structures/trash_pile.dm @@ -116,6 +116,8 @@ searched_by_ckeys[user.ckey] = 0 return + SEND_SIGNAL(user, COMSIG_LIVING_SEARCHED_TRASH_PILE, src) + if(searched_by_ckeys[user.ckey]) balloon_alert(user, "empty...") return TRUE diff --git a/tgstation.dme b/tgstation.dme index b8d18fcd22c..815dfc301ad 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -8949,6 +8949,7 @@ #include "modular_zubbers\code\datums\quirks\negative_quirks\blooddeficiency.dm" #include "modular_zubbers\code\datums\quirks\negative_quirks\bloodloss_dusting.dm" #include "modular_zubbers\code\datums\quirks\negative_quirks\brainproblems.dm" +#include "modular_zubbers\code\datums\quirks\negative_quirks\dirty.dm" #include "modular_zubbers\code\datums\quirks\negative_quirks\gifted.dm" #include "modular_zubbers\code\datums\quirks\negative_quirks\heavy_sleeper.dm" #include "modular_zubbers\code\datums\quirks\negative_quirks\loose_limbs.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/bubbers/dirty.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/bubbers/dirty.tsx new file mode 100644 index 00000000000..f33bf46ba70 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/bubbers/dirty.tsx @@ -0,0 +1,13 @@ +import { FeatureColorInput, FeatureTextInput, type Feature } from '../../base'; + +export const dirty_quirk_color: Feature = { + name: 'Dirt Color', + component: FeatureColorInput, +}; + +export const dirty_quirk_text: Feature = { + name: 'Flavor text', + description: + 'Displayed when you are dirty.', + component: FeatureTextInput, +};