diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index b7caf31c2cf..4e3f5a4cf28 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -25,10 +25,24 @@ ///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment #define COMSIG_CARBON_ATTACH_LIMB "carbon_attach_limb" #define COMPONENT_NO_ATTACH (1<<0) -#define COMSIG_CARBON_REMOVE_LIMB "carbon_remove_limb" //from base of /obj/item/bodypart/proc/drop_limb(lost_limb, dismembered) #define COMSIG_BODYPART_GAUZED "bodypart_gauzed" // from /obj/item/bodypart/proc/apply_gauze(/obj/item/stack/gauze) #define COMSIG_BODYPART_GAUZE_DESTROYED "bodypart_degauzed" // from [/obj/item/bodypart/proc/seep_gauze] when it runs out of absorption +/// Called from update_health_hud, whenever a bodypart is being updated on the health doll +#define COMSIG_BODYPART_UPDATING_HEALTH_HUD "bodypart_updating_health_hud" + /// Return to override that bodypart's health hud with your own icon + #define COMPONENT_OVERRIDE_BODYPART_HEALTH_HUD (1<<0) + +/// Called from /obj/item/bodypart/check_for_injuries (mob/living/carbon/examiner, list/check_list) +#define COMSIG_BODYPART_CHECKED_FOR_INJURY "bodypart_injury_checked" +/// Called from /obj/item/bodypart/check_for_injuries (obj/item/bodypart/examined, list/check_list) +#define COMSIG_CARBON_CHECKING_BODYPART "carbon_checking_injury" + +/// Called from carbon losing a limb /obj/item/bodypart/proc/drop_limb(obj/item/bodypart/lost_limb, dismembered) +#define COMSIG_CARBON_REMOVE_LIMB "carbon_remove_limb" +/// Called from bodypart being removed /obj/item/bodypart/proc/drop_limb(mob/living/carbon/old_owner, dismembered) +#define COMSIG_BODYPART_REMOVED "bodypart_removed" + ///from base of mob/living/carbon/soundbang_act(): (list(intensity)) #define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" ///from /item/organ/proc/Insert() (/obj/item/organ/) @@ -62,6 +76,10 @@ #define COMSIG_CARBON_LOSE_TRAUMA "carbon_lose_trauma" ///Called when a carbon updates their health (source = carbon) #define COMSIG_CARBON_HEALTH_UPDATE "carbon_health_update" +///Called when a carbon's health hud is updated. (source = carbon, shown_health_amount) +#define COMSIG_CARBON_UPDATING_HEALTH_HUD "carbon_health_hud_update" + /// Return if you override the carbon's health hud with something else + #define COMPONENT_OVERRIDE_HEALTH_HUD (1<<0) ///Called when a carbon updates their sanity (source = carbon) #define COMSIG_CARBON_SANITY_UPDATE "carbon_sanity_update" ///Called when a carbon breathes, before the breath has actually occured diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index aba1e9674ec..9965c4e4468 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -93,6 +93,11 @@ #define COMSIG_AIRLOCK_CLOSE "airlock_close" ///from /obj/machinery/door/airlock/set_bolt(): #define COMSIG_AIRLOCK_SET_BOLT "airlock_set_bolt" +///from /obj/machinery/door/airlock/bumpopen(), to the carbon who bumped: (airlock) +#define COMSIG_CARBON_BUMPED_AIRLOCK_OPEN "carbon_bumped_airlock_open" + /// Return to stop the door opening on bump. + #define STOP_BUMP (1<<0) + // /obj/item signals ///from base of obj/item/equipped(): (/mob/equipper, slot) diff --git a/code/__DEFINES/events.dm b/code/__DEFINES/events.dm index 2da887e95ad..d189969b8e3 100644 --- a/code/__DEFINES/events.dm +++ b/code/__DEFINES/events.dm @@ -32,3 +32,6 @@ #define EVENT_CATEGORY_SPACE "Space Threats" ///Events summoned by a wizard #define EVENT_CATEGORY_WIZARD "Wizard" + +/// Return from admin setup to stop the event from triggering entirely. +#define ADMIN_CANCEL_EVENT "cancel event" diff --git a/code/__DEFINES/research/anomalies.dm b/code/__DEFINES/research/anomalies.dm index 35ee0ec05ff..90be8dbb602 100644 --- a/code/__DEFINES/research/anomalies.dm +++ b/code/__DEFINES/research/anomalies.dm @@ -11,3 +11,6 @@ #define FLUX_NO_EXPLOSION 0 #define FLUX_EXPLOSIVE 1 #define FLUX_LOW_EXPLOSIVE 2 + +/// Chance of anomalies moving every process tick +#define ANOMALY_MOVECHANCE 45 diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 13220043713..68b65c54d65 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -38,3 +38,8 @@ #define STASIS_CHEMICAL_EFFECT "stasis_chemical" #define STASIS_SHAPECHANGE_EFFECT "stasis_shapechange" + +#define adjust_hallucinations(duration) adjust_timed_status_effect(duration, /datum/status_effect/hallucination) +#define adjust_hallucinations_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/hallucination, up_to) +#define set_hallucinations(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination) +#define set_hallucinations_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination, TRUE) diff --git a/code/__DEFINES/supermatter.dm b/code/__DEFINES/supermatter.dm index e3c68f7265f..6b9d52d665e 100644 --- a/code/__DEFINES/supermatter.dm +++ b/code/__DEFINES/supermatter.dm @@ -36,7 +36,7 @@ #define MATTER_POWER_CONVERSION 10 //Crystal converts 1/this value of stored matter into energy. //These would be what you would get at point blank, decreases with distance -#define DETONATION_HALLUCINATION 600 +#define DETONATION_HALLUCINATION 20 MINUTES /// All humans within this range will be irradiated #define DETONATION_RADIATION_RANGE 20 diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index c48dafc336a..4c4bec0e2a1 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -49,5 +49,7 @@ #define CULT_SHUTTLE_CURSE "cult_shuttle_curse.json" /// File location for eigenstasium lines #define EIGENSTASIUM_FILE "eigenstasium.json" +/// File location for hallucination lines +#define HALLUCINATION_FILE "hallucination.json" /// File location for ninja lines #define NINJA_FILE "ninja.json" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 7e0166096d0..3a04a8817cd 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -312,6 +312,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NICE_SHOT "nice_shot" /// prevents the damage done by a brain tumor #define TRAIT_TUMOR_SUPPRESSED "brain_tumor_suppressed" +/// Prevents hallucinations from the hallucination brain trauma (RDS) +#define TRAIT_HALLUCINATION_SUPPRESSED "hallucination_suppressed" /// overrides the update_fire proc to always add fire (for lava) #define TRAIT_PERMANENTLY_ONFIRE "permanently_onfire" /// Galactic Common Sign Language @@ -584,8 +586,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_EXTROVERT "extrovert" #define TRAIT_INTROVERT "introvert" #define TRAIT_ANXIOUS "anxious" -#define TRAIT_INSANITY "insanity" #define TRAIT_SMOKER "smoker" + /// Gives you the Shifty Eyes quirk, rarely making people who examine you think you examined them back even when you didn't #define TRAIT_SHIFTY_EYES "shifty_eyes" diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 58988ddb696..cc655f59ff5 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -126,12 +126,13 @@ #define VV_HK_GIVE_SPEECH_IMPEDIMENT "impede_speech" #define VV_HK_ADD_MOOD "addmood" #define VV_HK_REMOVE_MOOD "removemood" +#define VV_HK_GIVE_HALLUCINATION "give_hallucination" +#define VV_HK_GIVE_DELUSION_HALLUCINATION "give_hallucination_delusion" // /mob/living/carbon #define VV_HK_MAKE_AI "aiify" #define VV_HK_MODIFY_BODYPART "mod_bodypart" #define VV_HK_MODIFY_ORGANS "organs_modify" -#define VV_HK_HALLUCINATION "force_hallucinate" #define VV_HK_MARTIAL_ART "give_martial_art" #define VV_HK_GIVE_TRAUMA "give_trauma" #define VV_HK_CURE_TRAUMA "cure_trauma" diff --git a/code/__HELPERS/hallucinations.dm b/code/__HELPERS/hallucinations.dm new file mode 100644 index 00000000000..1c860ec616b --- /dev/null +++ b/code/__HELPERS/hallucinations.dm @@ -0,0 +1,236 @@ +/// A global list of all ongoing hallucinations, primarily for easy access to be able to stop (delete) hallucinations. +GLOBAL_LIST_EMPTY(all_ongoing_hallucinations) + +// Macro wrapper for _cause_hallucination so we can cheat in named arguments, like AddComponent. +/** + * Causes a hallucination of a certain type to the mob. + * + * First argument is always the type of halllucination, a /datum/hallucination, required. + * second argument is always the key source of the hallucination, used for admin logging, required. + * + * Additionally, named arguments are supported for passing them forward to the created hallucination's new(). + */ +#define cause_hallucination(arguments...) _cause_hallucination(list(##arguments)) + +/// Unless you need this for an explicit reason, use the cause_hallucination wrapper. +/mob/living/proc/_cause_hallucination(list/raw_args) + if(!length(raw_args)) + CRASH("cause_hallucination called with no arguments.") + + var/datum/hallucination/hallucination_type = raw_args[1] // first arg is the type always + if(!ispath(hallucination_type)) + CRASH("cause_hallucination was given a non-hallucination type.") + + var/hallucination_source = raw_args[2] // and second arg, the source + var/datum/hallucination/new_hallucination + + if(length(raw_args) > 2) + var/list/passed_args = raw_args.Copy(3) + passed_args.Insert(1, src) + + new_hallucination = new hallucination_type(arglist(passed_args)) + else + new_hallucination = new hallucination_type(src) + + // For some reason, we qdel'd in New, maybe something went wrong. + if(QDELETED(new_hallucination)) + return + // It's not guaranteed that the hallucination passed can successfully be initiated. + // This means there may be cases where someone should have a hallucination but nothing happens, + // notably if you pass a randomly picked hallucination type into this. + // Maybe there should be a separate proc to reroll on failure? + if(!new_hallucination.start()) + qdel(new_hallucination) + return + + investigate_log("was afflicted with a hallucination of type [hallucination_type] by: [hallucination_source]. \ + ([new_hallucination.feedback_details])", INVESTIGATE_HALLUCINATIONS) + return new_hallucination + +/** + * Emits a hallucinating pulse around the passed atom. + * Affects everyone in the passed radius who can view the center, + * except for those with TRAIT_MADNESS_IMMUNE, or those who are blind. + * + * center - required, the center of the pulse + * radius - the radius around that the pulse reaches + * hallucination_duration - how much hallucination is added by the pulse. reduced based on distance to the center. + * hallucination_max_duration - a cap on how much hallucination can be added + * optional_messages - optional list of messages passed. Those affected by pulses will be given one of the messages in said list. + */ +/proc/visible_hallucination_pulse(atom/center, radius = 7, hallucination_duration = 50 SECONDS, hallucination_max_duration, list/optional_messages) + for(var/mob/living/nearby_living in view(center, radius)) + if(HAS_TRAIT(nearby_living, TRAIT_MADNESS_IMMUNE) || (nearby_living.mind && HAS_TRAIT(nearby_living.mind, TRAIT_MADNESS_IMMUNE))) + continue + + if(nearby_living.is_blind()) + continue + + // Everyone else gets hallucinations. + var/dist = sqrt(1 / max(1, get_dist(nearby_living, center))) + nearby_living.adjust_hallucinations_up_to(hallucination_duration * dist, hallucination_max_duration) + if(length(optional_messages)) + to_chat(nearby_living, pick(optional_messages)) + +/// Global weighted list of all hallucinations that can show up randomly. +GLOBAL_LIST_INIT(random_hallucination_weighted_list, generate_hallucination_weighted_list()) + +/// Generates the global weighted list of random hallucinations. +/proc/generate_hallucination_weighted_list() + var/list/weighted_list = list() + + for(var/datum/hallucination/hallucination_type as anything in typesof(/datum/hallucination)) + if(hallucination_type == initial(hallucination_type.abstract_hallucination_parent)) + continue + var/weight = initial(hallucination_type.random_hallucination_weight) + if(weight <= 0) + continue + + weighted_list[hallucination_type] = weight + + return weighted_list + +/// Debug proc for getting the total weight of the random_hallucination_weighted_list +/proc/debug_hallucination_weighted_list() + var/total_weight = 0 + for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list) + total_weight += GLOB.random_hallucination_weighted_list[hallucination_type] + + to_chat(usr, span_boldnotice("The total weight of the hallucination weighted list is [total_weight].")) + return total_weight + +/// Debug verb for getting the weight of each distinct type within the random_hallucination_weighted_list +/client/proc/debug_hallucination_weighted_list_per_type() + set name = "Show Hallucination Weights" + set category = "Debug" + + var/header = "Type Weight Percent" + + var/total_weight = debug_hallucination_weighted_list() + var/list/all_weights = list() + var/datum/hallucination/last_type + var/last_type_weight = 0 + for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list) + var/this_weight = GLOB.random_hallucination_weighted_list[hallucination_type] + // Last_type is the abstract parent of the last hallucination type we iterated over + if(last_type) + // If this hallucination is the same path as the last type (subtype), add it to the total of the last type weight + if(ispath(hallucination_type, last_type)) + last_type_weight += this_weight + continue + + // Otherwise we moved onto the next hallucination subtype so we can stop + else + all_weights["[last_type] [last_type_weight] / [total_weight] [round(100 * (last_type_weight / total_weight), 0.01)]% chance"] = last_type_weight + + // Set last_type to the abstract parent of this hallucination + last_type = initial(hallucination_type.abstract_hallucination_parent) + // If last_type is the base hallucination it has no distinct subtypes so we can total it up immediately + if(last_type == /datum/hallucination) + all_weights["[hallucination_type] [this_weight] / [total_weight] [round(100 * (this_weight / total_weight), 0.01)]% chance"] = this_weight + last_type = null + + // Otherwise we start the weight sum for the next entry here + else + last_type_weight = this_weight + + // Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text + all_weights = sortTim(all_weights, /proc/cmp_numeric_dsc, associative = TRUE) + + var/page_style = "" + var/page_contents = "[page_style][header][jointext(assoc_to_keys(all_weights), "")]
" + var/datum/browser/popup = new(mob, "hallucinationdebug", "Hallucination Weights", 600, 400) + popup.set_content(page_contents) + popup.open() + +/// Gets a random subtype of the passed hallucination type that has a random_hallucination_weight > 0. +/// If no subtype is passed, it will get any random hallucination subtype that is not abstract and has weight > 0. +/// This can be used instead of picking from the global weighted list to just get a random valid hallucination. +/proc/get_random_valid_hallucination_subtype(passed_type = /datum/hallucination) + if(!ispath(passed_type, /datum/hallucination)) + CRASH("get_random_valid_hallucination_subtype - get_random_valid_hallucination_subtype passed not a hallucination subtype.") + + for(var/datum/hallucination/hallucination_type as anything in shuffle(subtypesof(passed_type))) + if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type) + continue + if(initial(hallucination_type.random_hallucination_weight) <= 0) + continue + + return hallucination_type + + return null + +/// Helper to give the passed mob the ability to select a hallucination from the list of all hallucination subtypes. +/proc/select_hallucination_type(mob/user, message = "Select a hallucination subtype", title = "Choose Hallucination") + var/static/list/hallucinations + if(!hallucinations) + hallucinations = typesof(/datum/hallucination) + for(var/datum/hallucination/hallucination_type as anything in hallucinations) + if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type) + hallucinations -= hallucination_type + + var/chosen = tgui_input_list(user, message, title, hallucinations) + if(!chosen || !ispath(chosen, /datum/hallucination)) + return null + + return chosen + +/// Helper to give the passed mob the ability to create a delusion hallucination (even a custom one). +/// Returns a list of arguments - pass these to _cause_hallucination to cause the desired hallucination +/proc/create_delusion(mob/user) + var/static/list/delusions + if(!delusions) + delusions = typesof(/datum/hallucination/delusion) + for(var/datum/hallucination/delusion_type as anything in delusions) + if(initial(delusion_type.abstract_hallucination_parent) == delusion_type) + delusions -= delusion_type + + var/chosen = tgui_input_list(user, "Select a delusion type. Custom will allow for custom icon entry.", "Select Delusion", delusions) + if(!chosen || !ispath(chosen, /datum/hallucination/delusion)) + return + + var/list/delusion_args = list() + var/static/list/options = list("Yes", "No") + var/duration = tgui_input_number(user, "How long should it last in seconds?", "Delusion: Duration", max_value = INFINITY, min_value = 1, default = 30) + var/affects_us = (tgui_alert(user, "Should they see themselves as the delusion?", "Delusion: Affects us", options) == "Yes") + var/affects_others = (tgui_alert(user, "Should they see everyone else delusion?", "Delusion: Affects others", options) == "Yes") + var/skip_nearby = (tgui_alert(user, "Should the delusion only affect people outside of their view?", "Delusion: Skip in view", options) == "Yes") + var/play_wabbajack = (tgui_alert(user, "Play the wabbajack sound when it happens?", "Delusion: Wabbajack sound", options) == "Yes") + + delusion_args = list( + chosen, + "forced delusion", + duration = duration * 1 SECONDS, + affects_us = affects_us, + affects_others = affects_others, + skip_nearby = skip_nearby, + play_wabbajack = play_wabbajack, + ) + + if(ispath(chosen, /datum/hallucination/delusion/custom)) + var/custom_icon_file = input(user, "Pick file for custom delusion:", "Custom Delusion: File") as null|file + if(!custom_icon_file) + return + + var/custom_icon_state = tgui_input_text(user, "What icon state do you wanna use from the file?", "Custom Delusion: Icon State") + if(!custom_icon_state) + return + + var/custom_name = tgui_input_text(user, "What name should it show up as? (Can be empty)", "Custom Delusion: Name") + + delusion_args += list( + custom_icon_file = custom_icon_file, + custom_icon_state = custom_icon_state, + custom_name = custom_name, + ) + + return delusion_args + +/// Lines the bubblegum hallucinatoin uses when it pops up +#define BUBBLEGUM_HALLUCINATION_LINES list( \ + span_colossus("I AM IMMORTAL."), \ + span_colossus("I SHALL TAKE YOUR WORLD."), \ + span_colossus("I SEE YOU."), \ + span_colossus("YOU CANNOT ESCAPE ME FOREVER."), \ + span_colossus("NOTHING CAN HOLD ME."), \ + ) diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index 532193aec6f..56b8b00dfe9 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -107,7 +107,7 @@ GLOBAL_LIST_INIT(phobia_objs, list( )), "spiders" = typecacheof(list(/obj/structure/spider)), "security" = typecacheof(list( - /obj/effect/hallucination/simple/securitron, + /obj/effect/client_image_holder/securitron, /obj/item/clothing/under/rank/security/detective, /obj/item/clothing/under/rank/security/head_of_security, /obj/item/clothing/under/rank/security/officer, diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm index 6e540489f93..d0e0bc2845a 100644 --- a/code/datums/brain_damage/hypnosis.dm +++ b/code/datums/brain_damage/hypnosis.dm @@ -58,11 +58,15 @@ /datum/brain_trauma/hypnosis/on_life(delta_time, times_fired) ..() if(DT_PROB(1, delta_time)) - switch(rand(1,2)) - if(1) - to_chat(owner, span_hypnophrase("...[lowertext(hypnotic_phrase)]...")) - if(2) - new /datum/hallucination/chat(owner, TRUE, FALSE, span_hypnophrase("[hypnotic_phrase]")) + if(prob(50)) + to_chat(owner, span_hypnophrase("...[lowertext(hypnotic_phrase)]...")) + else + owner.cause_hallucination( \ + /datum/hallucination/chat, \ + "hypnosis", \ + force_radio = TRUE, \ + specific_message = span_hypnophrase("[hypnotic_phrase]"), \ + ) /datum/brain_trauma/hypnosis/handle_hearing(datum/source, list/hearing_args) hearing_args[HEARING_RAW_MESSAGE] = target_phrase.Replace(hearing_args[HEARING_RAW_MESSAGE], span_hypnophrase("$1")) diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm index 70f3fa73497..31d02b408ac 100644 --- a/code/datums/brain_damage/magic.dm +++ b/code/datums/brain_damage/magic.dm @@ -73,12 +73,16 @@ scan_desc = "extra-sensory paranoia" gain_text = "You feel like something wants to kill you..." lose_text = "You no longer feel eyes on your back." - var/obj/effect/hallucination/simple/stalker_phantom/stalker + var/obj/effect/client_image_holder/stalker_phantom/stalker var/close_stalker = FALSE //For heartbeat +/datum/brain_trauma/magic/stalker/Destroy() + QDEL_NULL(stalker) + return ..() + /datum/brain_trauma/magic/stalker/on_gain() create_stalker() - ..() + return ..() /datum/brain_trauma/magic/stalker/proc/create_stalker() var/turf/stalker_source = locate(owner.x + pick(-12, 12), owner.y + pick(-12, 12), owner.z) //random corner @@ -86,7 +90,7 @@ /datum/brain_trauma/magic/stalker/on_lose() QDEL_NULL(stalker) - ..() + return ..() /datum/brain_trauma/magic/stalker/on_life(delta_time, times_fired) // Dead and unconscious people are not interesting to the psychic stalker. @@ -115,7 +119,7 @@ close_stalker = FALSE ..() -/obj/effect/hallucination/simple/stalker_phantom +/obj/effect/client_image_holder/stalker_phantom name = "???" desc = "It's coming closer..." image_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm index 09262e5940d..52e4a0b2766 100644 --- a/code/datums/brain_damage/mild.dm +++ b/code/datums/brain_damage/mild.dm @@ -8,16 +8,20 @@ name = "Hallucinations" desc = "Patient suffers constant hallucinations." scan_desc = "schizophrenia" - gain_text = "You feel your grip on reality slipping..." - lose_text = "You feel more grounded." + gain_text = span_warning("You feel your grip on reality slipping...") + lose_text = span_notice("You feel more grounded.") /datum/brain_trauma/mild/hallucinations/on_life(delta_time, times_fired) - owner.hallucination = min(owner.hallucination + 10, 50) - ..() + if(owner.stat != CONSCIOUS || owner.IsSleeping() || owner.IsUnconscious()) + return + if(HAS_TRAIT(owner, TRAIT_HALLUCINATION_SUPPRESSED)) + return + + owner.adjust_hallucinations_up_to(10 SECONDS * delta_time, 100 SECONDS) /datum/brain_trauma/mild/hallucinations/on_lose() - owner.hallucination = 0 - ..() + owner.remove_status_effect(/datum/status_effect/hallucination) + return ..() /datum/brain_trauma/mild/stuttering name = "Stuttering" @@ -109,17 +113,15 @@ lose_text = "You no longer feel perfectly healthy." /datum/brain_trauma/mild/healthy/on_gain() - owner.set_screwyhud(SCREWYHUD_HEALTHY) - ..() + owner.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + return ..() /datum/brain_trauma/mild/healthy/on_life(delta_time, times_fired) - owner.set_screwyhud(SCREWYHUD_HEALTHY) //just in case of hallucinations owner.adjustStaminaLoss(-2.5 * delta_time) //no pain, no fatigue - ..() /datum/brain_trauma/mild/healthy/on_lose() - owner.set_screwyhud(SCREWYHUD_NONE) - ..() + owner.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + return ..() /datum/brain_trauma/mild/muscle_weakness name = "Muscle Weakness" diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index 9aa71f91610..492712d39c4 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -200,7 +200,7 @@ if(3, 4) if(high_stress) to_chat(owner, span_warning("You're going mad with loneliness!")) - owner.hallucination += 30 + owner.adjust_hallucinations(60 SECONDS) else to_chat(owner, span_warning("You feel really lonely...")) @@ -307,7 +307,7 @@ scan_desc = "dyslexia" gain_text = "You have trouble reading or writing..." lose_text = "Your suddenly remember how to read and write." - + /datum/brain_trauma/severe/dyslexia/on_gain() ADD_TRAIT(owner, TRAIT_ILLITERATE, TRAUMA_TRAIT) ..() diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index eddf9156af7..ee110a96853 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -87,61 +87,64 @@ if(!second_turf) return - var/obj/effect/hallucination/simple/bluespace_stream/first = new(first_turf, owner) - var/obj/effect/hallucination/simple/bluespace_stream/second = new(second_turf, owner) + var/obj/effect/client_image_holder/bluespace_stream/first = new(first_turf, owner) + var/obj/effect/client_image_holder/bluespace_stream/second = new(second_turf, owner) first.linked_to = second second.linked_to = first - first.seer = owner - second.seer = owner -/obj/effect/hallucination/simple/bluespace_stream +/obj/effect/client_image_holder/bluespace_stream name = "bluespace stream" desc = "You see a hidden pathway through bluespace..." image_icon = 'icons/effects/effects.dmi' image_state = "bluestream" image_layer = ABOVE_MOB_LAYER image_plane = GAME_PLANE_UPPER - var/obj/effect/hallucination/simple/bluespace_stream/linked_to - var/mob/living/carbon/seer + var/obj/effect/client_image_holder/bluespace_stream/linked_to -/obj/effect/hallucination/simple/bluespace_stream/Initialize(mapload) +/obj/effect/client_image_holder/bluespace_stream/Initialize(mapload, list/mobs_which_see_us) . = ..() - QDEL_IN(src, 300) + QDEL_IN(src, 30 SECONDS) -/obj/effect/hallucination/simple/bluespace_stream/Destroy() +/obj/effect/client_image_holder/bluespace_stream/Destroy() if(!QDELETED(linked_to)) qdel(linked_to) linked_to = null - seer = null return ..() -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/effect/hallucination/simple/bluespace_stream/attack_hand(mob/user, list/modifiers) - if(user != seer || !linked_to) +/obj/effect/client_image_holder/bluespace_stream/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) return + + if(!(user in who_sees_us) || !linked_to) + return + var/slip_in_message = pick("slides sideways in an odd way, and disappears", "jumps into an unseen dimension",\ "sticks one leg straight out, wiggles [user.p_their()] foot, and is suddenly gone", "stops, then blinks out of reality", \ "is pulled into an invisible vortex, vanishing from sight") var/slip_out_message = pick("silently fades in", "leaps out of thin air","appears", "walks out of an invisible doorway",\ "slides out of a fold in spacetime") + to_chat(user, span_notice("You try to align with the bluespace stream...")) - if(do_after(user, 20, target = src)) - var/turf/source_turf = get_turf(src) - var/turf/destination_turf = get_turf(linked_to) + if(!do_after(user, 2 SECONDS, target = src)) + return - new /obj/effect/temp_visual/bluespace_fissure(source_turf) - new /obj/effect/temp_visual/bluespace_fissure(destination_turf) + var/turf/source_turf = get_turf(src) + var/turf/destination_turf = get_turf(linked_to) - user.visible_message(span_warning("[user] [slip_in_message]."), null, null, null, user) + new /obj/effect/temp_visual/bluespace_fissure(source_turf) + new /obj/effect/temp_visual/bluespace_fissure(destination_turf) - if(!do_teleport(user, destination_turf, no_effects = TRUE)) - user.visible_message(span_warning("[user] [slip_out_message], ending up exactly where they left."), null, null, null, user) - return + user.visible_message(span_warning("[user] [slip_in_message]."), ignored_mobs = user) + if(do_teleport(user, destination_turf, no_effects = TRUE)) user.visible_message(span_warning("[user] [slip_out_message]."), span_notice("...and find your way to the other side.")) + else + user.visible_message(span_warning("[user] [slip_out_message], ending up exactly where they left."), span_notice("...and find yourself where you started?")) -/obj/effect/hallucination/simple/bluespace_stream/attack_tk(mob/user) + +/obj/effect/client_image_holder/bluespace_stream/attack_tk(mob/user) to_chat(user, span_warning("\The [src] actively rejects your mind, and the bluespace energies surrounding it disrupt your telekinesis!")) return COMPONENT_CANCEL_ATTACK_CHAIN @@ -353,62 +356,73 @@ gain_text = "Justice is coming for you." lose_text = "You were absolved for your crimes." random_gain = FALSE - var/obj/effect/hallucination/simple/securitron/beepsky + /// A ref to our fake beepsky image that we chase the owner with + var/obj/effect/client_image_holder/securitron/beepsky + +/datum/brain_trauma/special/beepsky/Destroy() + QDEL_NULL(beepsky) + return ..() /datum/brain_trauma/special/beepsky/on_gain() create_securitron() - ..() + return ..() /datum/brain_trauma/special/beepsky/proc/create_securitron() + QDEL_NULL(beepsky) var/turf/where = locate(owner.x + pick(-12, 12), owner.y + pick(-12, 12), owner.z) beepsky = new(where, owner) - beepsky.victim = owner /datum/brain_trauma/special/beepsky/on_lose() QDEL_NULL(beepsky) - ..() + return ..() /datum/brain_trauma/special/beepsky/on_life() if(QDELETED(beepsky) || !beepsky.loc || beepsky.z != owner.z) - QDEL_NULL(beepsky) if(prob(30)) create_securitron() else return + if(get_dist(owner, beepsky) >= 10 && prob(20)) - QDEL_NULL(beepsky) create_securitron() + if(owner.stat != CONSCIOUS) if(prob(20)) owner.playsound_local(beepsky, 'sound/voice/beepsky/iamthelaw.ogg', 50) return + if(get_dist(owner, beepsky) <= 1) owner.playsound_local(owner, 'sound/weapons/egloves.ogg', 50) owner.visible_message(span_warning("[owner]'s body jerks as if it was shocked."), span_userdanger("You feel the fist of the LAW.")) owner.take_bodypart_damage(0,0,rand(40, 70)) QDEL_NULL(beepsky) + if(prob(20) && get_dist(owner, beepsky) <= 8) owner.playsound_local(beepsky, 'sound/voice/beepsky/criminal.ogg', 40) - ..() -/obj/effect/hallucination/simple/securitron +/obj/effect/client_image_holder/securitron name = "Securitron" desc = "The LAW is coming." image_icon = 'icons/mob/silicon/aibots.dmi' image_state = "secbot-c" - var/victim -/obj/effect/hallucination/simple/securitron/Initialize(mapload) +/obj/effect/client_image_holder/securitron/Initialize(mapload) . = ..() name = pick("Officer Beepsky", "Officer Johnson", "Officer Pingsky") START_PROCESSING(SSfastprocess, src) -/obj/effect/hallucination/simple/securitron/process() - if(prob(60)) - forceMove(get_step_towards(src, victim)) - if(prob(5)) - to_chat(victim, span_name("[name] exclaims, \"Level 10 infraction alert!\"")) - -/obj/effect/hallucination/simple/securitron/Destroy() +/obj/effect/client_image_holder/securitron/Destroy() STOP_PROCESSING(SSfastprocess,src) return ..() + +/obj/effect/client_image_holder/securitron/process() + if(prob(40)) + return + + var/mob/victim = pick(who_sees_us) + forceMove(get_step_towards(src, victim)) + if(prob(5)) + var/beepskys_cry = "Level 10 infraction alert!" + to_chat(victim, "[name] exclaims, \"[beepskys_cry]\"") + if(victim.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat)) + victim.create_chat_message(src, raw_message = beepskys_cry, spans = list("robotic")) diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm index cbf594d5e0f..96466782b66 100644 --- a/code/datums/diseases/advance/symptoms/hallucigen.dm +++ b/code/datums/diseases/advance/symptoms/hallucigen.dm @@ -61,4 +61,4 @@ to_chat(M, span_userdanger("[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]")) else to_chat(M, span_notice("[pick(healthy_messages)]")) - M.hallucination += (45 * power) + M.adjust_hallucinations(90 SECONDS * power) diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm index 2eab2b6a5bb..b0a5a7787a5 100644 --- a/code/datums/diseases/advance/symptoms/sensory.dm +++ b/code/datums/diseases/advance/symptoms/sensory.dm @@ -57,7 +57,8 @@ M.reagents.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) if(M.reagents.has_reagent(/datum/reagent/toxin/histamine)) M.reagents.remove_reagent(/datum/reagent/toxin/histamine, 5) - M.hallucination = max(0, M.hallucination - 10) + + M.adjust_hallucinations(-20 SECONDS) if(A.stage >= 5) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3) diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index 6767e389005..66f18394918 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -104,7 +104,7 @@ if(DT_PROB(2.5, delta_time) && owner.stat == CONSCIOUS) owner.emote("scream") if(prob(25)) - owner.hallucination += 20 + owner.adjust_hallucinations(40 SECONDS) //Dwarfism shrinks your body and lets you pass tables. /datum/mutation/human/dwarfism diff --git a/code/datums/quirks/negative.dm b/code/datums/quirks/negative.dm index aa4cddd2e77..2fb02a5dc1f 100644 --- a/code/datums/quirks/negative.dm +++ b/code/datums/quirks/negative.dm @@ -468,28 +468,41 @@ /datum/quirk/insanity name = "Reality Dissociation Syndrome" - desc = "You suffer from a severe disorder that causes very vivid hallucinations. Mindbreaker toxin can suppress its effects, and you are immune to mindbreaker's hallucinogenic properties. THIS IS NOT A LICENSE TO GRIEF." + desc = "You suffer from a severe disorder that causes very vivid hallucinations. \ + Mindbreaker toxin can suppress its effects, and you are immune to mindbreaker's hallucinogenic properties. \ + THIS IS NOT A LICENSE TO GRIEF." icon = "grin-tongue-wink" value = -8 - mob_trait = TRAIT_INSANITY - gain_text = "..." - lose_text = "You feel in tune with the world again." + gain_text = span_userdanger("...") + lose_text = span_notice("You feel in tune with the world again.") medical_record_text = "Patient suffers from acute Reality Dissociation Syndrome and experiences vivid hallucinations." hardcore_value = 6 - processing_quirk = TRUE -/datum/quirk/insanity/process(delta_time) - if(quirk_holder.stat != CONSCIOUS || quirk_holder.IsSleeping() || quirk_holder.IsUnconscious()) +/datum/quirk/insanity/add() + if(!iscarbon(quirk_holder)) return + var/mob/living/carbon/carbon_quirk_holder = quirk_holder - if(DT_PROB(2, delta_time)) - quirk_holder.hallucination += rand(10, 25) + // Setup our special RDS mild hallucination. + // Not a unique subtype so not to plague subtypesof, + // also as we inherit the names and values from our quirk. + var/datum/brain_trauma/mild/hallucinations/added_trauma = new() + added_trauma.resilience = TRAUMA_RESILIENCE_ABSOLUTE + added_trauma.name = name + added_trauma.desc = medical_record_text + added_trauma.scan_desc = lowertext(name) + added_trauma.gain_text = null + added_trauma.lose_text = null -/datum/quirk/insanity/post_add() //I don't /think/ we'll need this but for newbies who think "roleplay as insane" = "license to kill" it's probably a good thing to have + carbon_quirk_holder.gain_trauma(added_trauma) + +/datum/quirk/insanity/post_add() if(!quirk_holder.mind || quirk_holder.mind.special_role) return - to_chat(quirk_holder, "Please note that your dissociation syndrome does NOT give you the right to attack people or otherwise cause any interference to \ - the round. You are not an antagonist, and the rules will treat you the same as other crewmembers.") + // I don't /think/ we'll need this, but for newbies who think "roleplay as insane" = "license to kill", + // it's probably a good thing to have. + to_chat(quirk_holder, "Please note that your [lowertext(name)] does NOT give you the right to attack people or otherwise cause any interference to \ + the round. You are not an antagonist, and the rules will treat you the same as other crewmembers.") /datum/quirk/social_anxiety name = "Social Anxiety" diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index c4c1f7582f7..a1385b90dbf 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -378,17 +378,30 @@ . = ..() to_chat(owner, "RIP AND TEAR") SEND_SOUND(owner, sound('sound/hallucinations/veryfar_noise.ogg')) - new /datum/hallucination/delusion(owner, forced = TRUE, force_kind = "demon", duration = duration, skip_nearby = FALSE) - chainsaw = new(get_turf(owner)) - owner.log_message("entered a blood frenzy", LOG_ATTACK) - ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) + owner.cause_hallucination( \ + /datum/hallucination/delusion/preset/demon, \ + "[id] status effect", \ + duration = duration, \ + affects_us = FALSE, \ + affects_others = TRUE, \ + skip_nearby = FALSE, \ + play_wabbajack = FALSE, \ + ) + owner.drop_all_held_items() + + chainsaw = new(get_turf(owner)) + ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) owner.put_in_hands(chainsaw, forced = TRUE) chainsaw.attack_self(owner) - owner.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25) + + owner.log_message("entered a blood frenzy", LOG_ATTACK) + owner.reagents.add_reagent(/datum/reagent/medicine/adminordrazine, 25) to_chat(owner, span_warning("KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!")) + var/datum/client_colour/colour = owner.add_client_colour(/datum/client_colour/bloodlust) QDEL_IN(colour, 1.1 SECONDS) + return TRUE /datum/status_effect/mayhem/on_remove() . = ..() diff --git a/code/datums/status_effects/debuffs/hallucination.dm b/code/datums/status_effects/debuffs/hallucination.dm new file mode 100644 index 00000000000..123f2332bab --- /dev/null +++ b/code/datums/status_effects/debuffs/hallucination.dm @@ -0,0 +1,91 @@ +/// Hallucination status effect. How most hallucinations end up happening. +/// Hallucinations are drawn from the global weighted list, random_hallucination_weighted_list +/datum/status_effect/hallucination + id = "hallucination" + alert_type = null + tick_interval = 2 SECONDS + /// Can this hallucination apply to silicons? + var/affects_silicons = FALSE + /// The lower range of when the next hallucination will trigger after one occurs. + var/lower_tick_interval = 10 SECONDS + /// The upper range of when the next hallucination will trigger after one occurs. + var/upper_tick_interval = 60 SECONDS + /// The cooldown for when the next hallucination can occur + COOLDOWN_DECLARE(hallucination_cooldown) + +/datum/status_effect/hallucination/on_creation( + mob/living/new_owner, + duration = 10 SECONDS, + affects_silicons = FALSE, +) + + src.duration = duration + src.affects_silicons = affects_silicons + return ..() + +/datum/status_effect/hallucination/on_apply() + if(!affects_silicons && issilicon(owner)) + return FALSE + + RegisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL, .proc/remove_hallucinations) + RegisterSignal(owner, COMSIG_LIVING_HEALTHSCAN, .proc/on_health_scan) + if(iscarbon(owner)) + RegisterSignal(owner, COMSIG_CARBON_CHECKING_BODYPART, .proc/on_check_bodypart) + RegisterSignal(owner, COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, .proc/on_bump_airlock) + + return TRUE + +/datum/status_effect/hallucination/on_remove() + UnregisterSignal(owner, list( + COMSIG_LIVING_POST_FULLY_HEAL, + COMSIG_LIVING_HEALTHSCAN, + COMSIG_CARBON_CHECKING_BODYPART, + COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, + )) + +/// Signal proc for [COMSIG_LIVING_POST_FULLY_HEAL], terminate on full heal +/datum/status_effect/hallucination/proc/remove_hallucinations(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/// Signal proc for [COMSIG_LIVING_HEALTHSCAN]. Show we're hallucinating to (advanced) scanners. +/datum/status_effect/hallucination/proc/on_health_scan(datum/source, list/render_list, advanced, mob/user, mode) + SIGNAL_HANDLER + + if(!advanced) + return + + render_list += "Subject is hallucinating.\n" + +/// Signal proc for [COMSIG_CARBON_CHECKING_BODYPART], +/// checking bodyparts while hallucinating can cause them to appear more damaged than they are +/datum/status_effect/hallucination/proc/on_check_bodypart(mob/living/carbon/source, obj/item/bodypart/examined, list/check_list, list/limb_damage) + SIGNAL_HANDLER + + if(prob(30)) + limb_damage[BRUTE] += rand(30, 40) + if(prob(30)) + limb_damage[BURN] += rand(30, 40) + +/// Signal proc for [COMSIG_CARBON_BUMPED_AIRLOCK_OPEN], bumping an airlock can cause a fake zap. +/// This only happens on airlock bump, future TODO - make this chance roll for attack_hand opening airlocks too +/datum/status_effect/hallucination/proc/on_bump_airlock(mob/living/carbon/source, obj/machinery/door/airlock/bumped) + SIGNAL_HANDLER + + // 1% chance to fake a shock. + if(prob(99) || !source.should_electrocute() || bumped.operating) + return + + source.cause_hallucination(/datum/hallucination/shock, "hallucinated shock from [bumped]",) + return STOP_BUMP + +/datum/status_effect/hallucination/tick(delta_time, times_fired) + if(owner.stat == DEAD) + return + if(!COOLDOWN_FINISHED(src, hallucination_cooldown)) + return + + var/datum/hallucination/picked_hallucination = pick_weight(GLOB.random_hallucination_weighted_list) + owner.cause_hallucination(picked_hallucination, "[id] status effect") + COOLDOWN_START(src, hallucination_cooldown, rand(lower_tick_interval, upper_tick_interval)) diff --git a/code/datums/status_effects/debuffs/screwy_hud.dm b/code/datums/status_effects/debuffs/screwy_hud.dm new file mode 100644 index 00000000000..a270652262a --- /dev/null +++ b/code/datums/status_effects/debuffs/screwy_hud.dm @@ -0,0 +1,58 @@ +/** + * Screwy hud status. + * + * Applied to carbons, it will make their health bar look like it's incorrect - + * in crit (SCREWYHUD_CRIT), dead (SCREWYHUD_DEAD), or fully healthy (SCREWYHUD_HEALTHY) + * + * Grouped status effect, so multiple sources can add a screwyhud without + * accidentally removing another source's hud. + */ +/datum/status_effect/grouped/screwy_hud + alert_type = null + /// The priority of this screwyhud over other screwyhuds. + var/priority = -1 + /// The icon we override our owner's healths.icon_state with + var/override_icon + +/datum/status_effect/grouped/screwy_hud/on_apply() + if(!iscarbon(owner)) + return FALSE + + RegisterSignal(owner, COMSIG_CARBON_UPDATING_HEALTH_HUD, .proc/on_health_hud_updated) + owner.update_health_hud() + return TRUE + +/datum/status_effect/grouped/screwy_hud/on_remove() + UnregisterSignal(owner, COMSIG_CARBON_UPDATING_HEALTH_HUD) + owner.update_health_hud() + +/datum/status_effect/grouped/screwy_hud/proc/on_health_hud_updated(mob/living/carbon/source, shown_health_amount) + SIGNAL_HANDLER + + // Shouldn't even be running if we're dead, but just in case... + if(source.stat == DEAD) + return + + // It's entirely possible we have multiple screwy huds on one mob. + // Defer to priority to determine which to show. If our's is lower, don't show it. + for(var/datum/status_effect/grouped/screwy_hud/other_screwy_hud in source.status_effects) + if(other_screwy_hud.priority > priority) + return + + source.hud_used.healths.icon_state = override_icon + return COMPONENT_OVERRIDE_HEALTH_HUD + +/datum/status_effect/grouped/screwy_hud/fake_dead + id = "fake_hud_dead" + priority = 100 // death is absolute + override_icon = "health7" + +/datum/status_effect/grouped/screwy_hud/fake_crit + id = "fake_hud_crit" + priority = 90 // crit is almost death, and death is absolute + override_icon = "health6" + +/datum/status_effect/grouped/screwy_hud/fake_healthy + id = "fake_hud_healthy" + priority = 10 // fully healthy is the opposite of death, which is absolute + override_icon = "health0" diff --git a/code/datums/voice_of_god_command.dm b/code/datums/voice_of_god_command.dm index 4f12b007ab5..18dcf9941b9 100644 --- a/code/datums/voice_of_god_command.dm +++ b/code/datums/voice_of_god_command.dm @@ -161,8 +161,15 @@ GLOBAL_LIST_INIT(voice_of_god_commands, init_voice_of_god_commands()) trigger = "see\\s*the\\s*truth|hallucinate" /datum/voice_of_god_command/hallucinate/execute(list/listeners, mob/living/user, power_multiplier = 1, message) - for(var/mob/living/carbon/target in listeners) - new /datum/hallucination/delusion(target, TRUE, null, 150 * power_multiplier, 0) + for(var/mob/living/target in listeners) + target.cause_hallucination( \ + get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset), \ + "voice of god", \ + duration = 15 SECONDS * power_multiplier, \ + affects_us = FALSE, \ + affects_others = TRUE, \ + skip_nearby = FALSE, \ + ) /// This command wakes up the listeners. /datum/voice_of_god_command/wake_up diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index cb8dd26894e..6ae4b1822b9 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -264,7 +264,7 @@ qdel(L.pulling) var/turf/LA = get_turf(pick(warp_points)) L.forceMove(LA) - L.hallucination = 0 + L.remove_status_effect(/datum/status_effect/hallucination) to_chat(L, "The battle is won. Your bloodlust subsides.", confidential = TRUE) for(var/obj/item/chainsaw/doomslayer/chainsaw in L) qdel(chainsaw) diff --git a/code/game/machinery/computer/arcade/orion_event.dm b/code/game/machinery/computer/arcade/orion_event.dm index 9db3d00dab3..03da88e601b 100644 --- a/code/game/machinery/computer/arcade/orion_event.dm +++ b/code/game/machinery/computer/arcade/orion_event.dm @@ -260,7 +260,7 @@ /datum/orion_event/raiders/emag_effect(obj/machinery/computer/arcade/orion_trail/game, mob/living/gamer) if(prob(50-gamer_skill)) to_chat(usr, span_userdanger("You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?")) - gamer.hallucination += 30 + gamer.adjust_hallucinations(60 SECONDS) else to_chat(usr, span_userdanger("Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there...")) gamer.take_bodypart_damage(30) @@ -529,4 +529,3 @@ spaceport_security.GiveTarget(usr) game.fuel += fuel game.food += food - diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 4b03cf1798c..096a3b5bba7 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -353,16 +353,17 @@ seal = null update_appearance() -/obj/machinery/door/airlock/bumpopen(mob/living/user) //Airlocks now zap you when you 'bump' them open when they're electrified. --NeoFite - if(!issilicon(usr)) - if(isElectrified() && shock(user, 100)) - return - else if(user.hallucinating() && iscarbon(user) && prob(1) && !operating) - var/mob/living/carbon/C = user - if(!C.wearing_shock_proof_gloves()) - new /datum/hallucination/shock(C) - return - ..() +/obj/machinery/door/airlock/bumpopen(mob/living/user) + if(issilicon(user) || !iscarbon(user)) + return ..() + + if(isElectrified() && shock(user, 100)) + return + + if(SEND_SIGNAL(user, COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, src) & STOP_BUMP) + return + + return ..() /obj/machinery/door/airlock/proc/isElectrified() return (secondsElectrified != MACHINE_NOT_ELECTRIFIED) diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm index 629f5073918..d0f55d520a7 100644 --- a/code/game/machinery/medical_kiosk.dm +++ b/code/game/machinery/medical_kiosk.dm @@ -277,7 +277,7 @@ for(var/datum/addiction/addiction_type as anything in patient.mind.active_addictions) addict_list += list(list("name" = initial(addiction_type.name))) - if (patient.hallucinating()) + if (patient.has_status_effect(/datum/status_effect/hallucination)) hallucination_status = "Subject appears to be hallucinating. Suggested treatments: bedrest, mannitol or psicodine." if(patient.stat == DEAD || HAS_TRAIT(patient, TRAIT_FAKEDEATH) || ((brute_loss+fire_loss+tox_loss+oxy_loss+clone_loss) >= 200)) //Patient status checks. @@ -288,8 +288,16 @@ patient_status = "Injured" else if((brute_loss+fire_loss+tox_loss+oxy_loss+clone_loss) >= 20) patient_status = "Lightly Injured" - if(pandemonium || user.hallucinating()) - patient_status = pick("The only kiosk is kiosk, but is the only patient, patient?", "Breathing manually.","Constact NTOS site admin.","97% carbon, 3% natural flavoring","The ebb and flow wears us all in time.","It's Lupus. You have Lupus.","Undergoing monkey disease.") + if(pandemonium || user.has_status_effect(/datum/status_effect/hallucination)) + patient_status = pick( + "The only kiosk is kiosk, but is the only patient, patient?", + "Breathing manually.", + "Constact NTOS site admin.", + "97% carbon, 3% natural flavoring", + "The ebb and flow wears us all in time.", + "It's Lupus. You have Lupus.", + "Undergoing monkey disease.", + ) if((brain_loss) >= 100) //Brain status checks. brain_status = "Grave brain damage detected." @@ -300,9 +308,9 @@ else if((brain_loss) >= 1) brain_status = "Mild brain damage detected." //You may have a miiiild case of severe brain damage. - if(pandemonium == TRUE) + if(pandemonium) chaos_modifier = 1 - else if (user.hallucinating()) + else if(user.has_status_effect(/datum/status_effect/hallucination)) chaos_modifier = 0.3 data["kiosk_cost"] = active_price + (chaos_modifier * (rand(1,25))) diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index f045d1079ab..8d2ee355b1b 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -1,8 +1,4 @@ //Anomalies, used for events. Note that these DO NOT work by themselves; their procs are called by the event datum. - -/// Chance of taking a step per second -#define ANOMALY_MOVECHANCE 45 - /obj/effect/anomaly name = "anomaly" desc = "A mysterious anomaly, seen commonly only in the region of space that the station orbits..." @@ -521,6 +517,13 @@ var/ticks = 0 /// How many seconds between each small hallucination pulses var/release_delay = 5 + /// Messages sent to people feeling the pulses + var/static/list/messages = list( + span_warning("You feel your conscious mind fall apart!"), + span_warning("Reality warps around you!"), + span_warning("Something's wispering around you!"), + span_warning("You are going insane!"), + ) /obj/effect/anomaly/hallucination/anomalyEffect(delta_time) . = ..() @@ -528,36 +531,28 @@ if(ticks < release_delay) return ticks -= release_delay - var/turf/open/our_turf = get_turf(src) - if(istype(our_turf)) - hallucination_pulse(our_turf, 5) + if(!isturf(loc)) + return + + visible_hallucination_pulse( + center = get_turf(src), + radius = 5, + hallucination_duration = 50 SECONDS, + hallucination_max_duration = 300 SECONDS, + optional_messages = messages, + ) /obj/effect/anomaly/hallucination/detonate() - var/turf/open/our_turf = get_turf(src) - if(istype(our_turf)) - hallucination_pulse(our_turf, 10) + if(!isturf(loc)) + return -/obj/effect/anomaly/hallucination/proc/hallucination_pulse(turf/open/location, range) - for(var/mob/living/carbon/human/near in view(location, range)) - // If they are immune to hallucinations. - if (HAS_TRAIT(near, TRAIT_MADNESS_IMMUNE) || (near.mind && HAS_TRAIT(near.mind, TRAIT_MADNESS_IMMUNE))) - continue - - // Blind people don't get hallucinations. - if (near.is_blind()) - continue - - // Everyone else gets hallucinations. - var/dist = sqrt(1 / max(1, get_dist(near, location))) - near.hallucination += 50 * dist - near.hallucination = clamp(near.hallucination, 0, 150) - var/list/messages = list( - "You feel your conscious mind fall apart!", - "Reality warps around you!", - "Something's wispering around you!", - "You are going insane!", - ) - to_chat(near, span_warning(pick(messages))) + visible_hallucination_pulse( + center = get_turf(src), + radius = 10, + hallucination_duration = 50 SECONDS, + hallucination_max_duration = 300 SECONDS, + optional_messages = messages, + ) ///////////////////// @@ -641,5 +636,3 @@ icon = 'icons/effects/effects.dmi' icon_state = "shield-flash" duration = 3 - -#undef ANOMALY_MOVECHANCE diff --git a/code/game/objects/items/devices/portable_chem_mixer.dm b/code/game/objects/items/devices/portable_chem_mixer.dm index cf1cd98f4d5..61a69a4f96f 100644 --- a/code/game/objects/items/devices/portable_chem_mixer.dm +++ b/code/game/objects/items/devices/portable_chem_mixer.dm @@ -145,9 +145,16 @@ ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "PortableChemMixer", name) - if(user.hallucinating()) + + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + + if(is_hallucinating) // to not ruin the immersion by constantly changing the fake chemicals ui.set_autoupdate(FALSE) + ui.open() /obj/item/storage/portable_chem_mixer/ui_data(mob/user) @@ -159,9 +166,11 @@ data["beakerTransferAmounts"] = beaker ? list(1,5,10,30,50,100) : null data["showpH"] = show_ph var/chemicals[0] - var/is_hallucinating = user.hallucinating() - if(user.hallucinating()) - is_hallucinating = TRUE + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + for(var/re in dispensable_reagents) var/value = dispensable_reagents[re] var/datum/reagent/temp = GLOB.chemical_reagents_list[re] diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index bd5be4f616b..bb859f9a3af 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -188,9 +188,6 @@ if (HAS_TRAIT(target, TRAIT_IRRADIATED)) render_list += "Subject is irradiated. Supply toxin healing.\n" - if(advanced && target.hallucinating()) - render_list += "Subject is hallucinating.\n" - //Eyes and ears if(advanced && iscarbon(target)) var/mob/living/carbon/carbontarget = target diff --git a/code/game/objects/items/food/misc.dm b/code/game/objects/items/food/misc.dm index a1942c827b9..3373cfae381 100644 --- a/code/game/objects/items/food/misc.dm +++ b/code/game/objects/items/food/misc.dm @@ -442,8 +442,6 @@ food_reagents = list(/datum/reagent/blood = 15) tastes = list("hell" = 1, "people" = 1) metabolization_amount = REAGENTS_METABOLISM - /// What the player hears from the bubblegum hallucination, and also says one of these when suiciding - var/static/list/hallucination_lines = list("I AM IMMORTAL.", "I SHALL TAKE YOUR WORLD.", "I SEE YOU.", "YOU CANNOT ESCAPE ME FOREVER.", "NOTHING CAN HOLD ME.") /obj/item/food/bubblegum/bubblegum/process() . = ..() @@ -470,16 +468,15 @@ ///This proc has a 5% chance to have a bubblegum line appear, with an 85% chance for just text and 15% for a bubblegum hallucination and scarier text. /obj/item/food/bubblegum/bubblegum/proc/hallucinate(mob/living/carbon/victim) - if(!prob(5)) //cursed by bubblegum + if(prob(95)) //cursed by bubblegum return if(prob(15)) - new /datum/hallucination/oh_yeah(victim) - to_chat(victim, span_colossus("[pick(hallucination_lines)]")) + victim.cause_hallucination(/datum/hallucination/oh_yeah, "bubblegum bubblegum", haunt_them = TRUE) else to_chat(victim, span_warning("[pick("You hear faint whispers.", "You smell ash.", "You feel hot.", "You hear a roar in the distance.")]")) /obj/item/food/bubblegum/bubblegum/suicide_act(mob/user) - user.say(";[pick(hallucination_lines)]") + user.say(";[pick(BUBBLEGUM_HALLUCINATION_LINES)]") return ..() /obj/item/food/gumball diff --git a/code/game/objects/items/grenades/hypno.dm b/code/game/objects/items/grenades/hypno.dm index 36512b92ba2..074a94c6f12 100644 --- a/code/game/objects/items/grenades/hypno.dm +++ b/code/game/objects/items/grenades/hypno.dm @@ -46,14 +46,15 @@ living_mob.Paralyze(10) living_mob.Knockdown(100) to_chat(living_mob, span_hypnophrase("The sound echoes in your brain...")) - living_mob.hallucination += 50 + living_mob.adjust_hallucinations(100 SECONDS) + else if(distance <= 1) living_mob.Paralyze(5) living_mob.Knockdown(30) if(hypno_sound) to_chat(living_mob, span_hypnophrase("The sound echoes in your brain...")) - living_mob.hallucination += 50 + living_mob.adjust_hallucinations(100 SECONDS) //Flash if(living_mob.flash_act(affect_silicon = 1)) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 5d82efe3e58..912f056518c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -205,6 +205,7 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/open_lua_editor, /client/proc/validate_puzzgrids, /client/proc/debug_spell_requirements, + /client/proc/debug_hallucination_weighted_list_per_type, ) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release)) GLOBAL_PROTECT(admin_verbs_possess) diff --git a/code/modules/admin/force_event.dm b/code/modules/admin/force_event.dm index 86d400c3c9b..b4a099e4ff5 100644 --- a/code/modules/admin/force_event.dm +++ b/code/modules/admin/force_event.dm @@ -61,7 +61,8 @@ var/datum/round_event_control/event = locate(event_to_run_type) in SSevents.control if(!event) return - event.admin_setup(usr) + if(event.admin_setup(usr) == ADMIN_CANCEL_EVENT) + return var/always_announce_chance = 100 var/no_announce_chance = 0 event.runEvent(announce_chance_override = announce_event ? always_announce_chance : no_announce_chance, admin_forced = TRUE) diff --git a/code/modules/antagonists/abductor/equipment/glands/mindshock.dm b/code/modules/antagonists/abductor/equipment/glands/mindshock.dm index 381d7e11218..5b95c609e98 100644 --- a/code/modules/antagonists/abductor/equipment/glands/mindshock.dm +++ b/code/modules/antagonists/abductor/equipment/glands/mindshock.dm @@ -28,7 +28,7 @@ target.adjust_timed_status_effect(15 SECONDS, /datum/status_effect/confusion) target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160) if(3) - target.hallucination += 60 + target.adjust_hallucinations(120 SECONDS) /obj/item/organ/internal/heart/gland/mindshock/mind_control(command, mob/living/user) if(!ownerCheck() || !mind_control_uses || active_mind_control) diff --git a/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm b/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm index 9b023936a55..d1583f2443d 100644 --- a/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm +++ b/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm @@ -24,13 +24,15 @@ exposed_mob.reagents.add_reagent(/datum/reagent/toxin/spore, 0.2*reac_volume) exposed_mob.apply_damage(0.7*reac_volume, TOX) -/datum/reagent/blob/regenerative_materia/on_mob_life(mob/living/carbon/C, delta_time, times_fired) - C.adjustToxLoss(1 * REAGENTS_EFFECT_MULTIPLIER * delta_time) - C.hal_screwyhud = SCREWYHUD_HEALTHY //fully healed, honest +/datum/reagent/blob/regenerative_materia/on_mob_life(mob/living/carbon/metabolizer, delta_time, times_fired) + metabolizer.adjustToxLoss(1 * REAGENTS_EFFECT_MULTIPLIER * delta_time) ..() + return TRUE -/datum/reagent/blob/regenerative_materia/on_mob_end_metabolize(mob/living/M) - if(iscarbon(M)) - var/mob/living/carbon/N = M - N.hal_screwyhud = 0 - ..() +/datum/reagent/blob/regenerative_materia/on_mob_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/reagent/blob/regenerative_materia/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index 55c35bb9a8e..9b9fdf63dd3 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -218,12 +218,13 @@ /datum/action/changeling/sting/lsd/sting_action(mob/user, mob/living/carbon/target) log_combat(user, target, "stung", "LSD sting") - addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(300,600)) + addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(30 SECONDS, 60 SECONDS)) return TRUE /datum/action/changeling/sting/lsd/proc/hallucination_time(mob/living/carbon/target) - if(target) - target.hallucination = max(90, target.hallucination) + if(QDELETED(src) || QDELETED(target)) + return + target.adjust_hallucinations(180 SECONDS) /datum/action/changeling/sting/cryo name = "Cryogenic Sting" diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index c99cadd30ba..7c80d854b1f 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -239,7 +239,7 @@ /datum/action/innate/cult/blood_spell/horror/do_ability(mob/living/caller, params, mob/living/carbon/human/clicked_on) - clicked_on.hallucination = max(clicked_on.hallucination, 120) + clicked_on.set_hallucinations_if_lower(240 SECONDS) SEND_SOUND(caller, sound('sound/effects/ghost.ogg', FALSE, TRUE, 50)) var/image/sparkle_image = image('icons/effects/cult/effects.dmi', clicked_on, "bloodsparkles", ABOVE_MOB_LAYER) diff --git a/code/modules/antagonists/heretic/items/madness_mask.dm b/code/modules/antagonists/heretic/items/madness_mask.dm index 82b94b25d07..636aea1891a 100644 --- a/code/modules/antagonists/heretic/items/madness_mask.dm +++ b/code/modules/antagonists/heretic/items/madness_mask.dm @@ -61,7 +61,7 @@ human_in_range.mob_mood.direct_sanity_drain(rand(-2, -20) * delta_time) if(DT_PROB(60, delta_time)) - human_in_range.hallucination = min(human_in_range.hallucination + 5, 120) + human_in_range.adjust_hallucinations_up_to(10 SECONDS, 240 SECONDS) if(DT_PROB(40, delta_time)) human_in_range.set_timed_status_effect(10 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm index fbf9f0c2b1a..811189b378a 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm @@ -47,7 +47,7 @@ if(owner.health > owner.crit_threshold && prob(4)) owner.set_timed_status_effect(20 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) owner.set_timed_status_effect(10 SECONDS, /datum/status_effect/dizziness, only_if_higher = TRUE) - owner.hallucination = min(owner.hallucination + 3, 24) + owner.adjust_hallucinations_up_to(6 SECONDS, 48 SECONDS) if(prob(2)) playsound(owner, pick(GLOB.creepy_ambience), 50, TRUE) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm index 68b97b8f254..3bc8f3bb96d 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm @@ -225,13 +225,13 @@ sac_target.visible_message(span_danger("[sac_target] begins to shudder violenty as dark tendrils begin to drag them into thin air!")) sac_target.set_handcuffed(new /obj/item/restraints/handcuffs/energy/cult(sac_target)) sac_target.update_handcuffed() - + if(sac_target.legcuffed) sac_target.legcuffed.forceMove(sac_target.drop_location()) sac_target.legcuffed.dropped(sac_target) sac_target.legcuffed = null sac_target.update_worn_legcuffs() - + sac_target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 85, 150) sac_target.do_jitter_animation() log_combat(heretic_mind.current, sac_target, "sacrificed") @@ -320,7 +320,7 @@ sac_target.blur_eyes(15) sac_target.set_timed_status_effect(20 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) sac_target.set_timed_status_effect(20 SECONDS, /datum/status_effect/dizziness, only_if_higher = TRUE) - sac_target.hallucination += 12 + sac_target.adjust_hallucinations(24 SECONDS) sac_target.emote("scream") to_chat(sac_target, span_reallybig(span_hypnophrase("The grasp of the Mansus reveal themselves to you!"))) diff --git a/code/modules/antagonists/heretic/knowledge/void_lore.dm b/code/modules/antagonists/heretic/knowledge/void_lore.dm index cb3996d25e4..992ca434022 100644 --- a/code/modules/antagonists/heretic/knowledge/void_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/void_lore.dm @@ -195,7 +195,7 @@ /datum/heretic_knowledge/final/void_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) . = ..() - priority_announce("[generate_heretic_text()] The nobleman of void [user.real_name] has arrived, step along the Waltz that ends worlds! [generate_heretic_text()]","[generate_heretic_text()]", ANNOUNCER_SPANOMALIES) + priority_announce("[generate_heretic_text()] The nobleman of void [user.real_name] has arrived, stepping along the Waltz that ends worlds! [generate_heretic_text()]","[generate_heretic_text()]", ANNOUNCER_SPANOMALIES) user.client?.give_award(/datum/award/achievement/misc/void_ascension, user) ADD_TRAIT(user, TRAIT_RESISTLOWPRESSURE, MAGIC_TRAIT) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 55951226f33..36d7d2efe4e 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -1141,8 +1141,7 @@ for(var/i in 1 to nuclear_particle_amount) location.fire_nuclear_particle() radiation_pulse(location, max_range = min(sqrt(consumed_amount - nuclear_particle_amount * PN_BZASE_NUCLEAR_PARTICLE_RADIATION_ENERGY_CONVERSION) / PN_BZASE_RAD_RANGE_DIVISOR, GAS_REACTION_MAXIMUM_RADIATION_PULSE_RANGE), threshold = PN_BZASE_RAD_THRESHOLD) - for(var/mob/living/carbon/L in location) - L.hallucination += consumed_amount + visible_hallucination_pulse(location, 1, consumed_amount * 2 SECONDS) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm index 4fde03920d2..026aa26e3c0 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm @@ -325,7 +325,8 @@ internal_output.assert_gases(/datum/gas/healium, /datum/gas/proto_nitrate) internal_output.gases[/datum/gas/proto_nitrate][MOLES] += scaled_production * 1.5 internal_output.gases[/datum/gas/healium][MOLES] += scaled_production * 1.5 - induce_hallucination(50 * power_level, delta_time) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * delta_time) + if(5) if(moderator_list[/datum/gas/plasma] > 15) internal_output.assert_gases(/datum/gas/freon) @@ -344,7 +345,7 @@ if(moderator_list[/datum/gas/bz] > 100) internal_output.assert_gases(/datum/gas/healium, /datum/gas/freon) internal_output.gases[/datum/gas/healium][MOLES] += scaled_production - induce_hallucination(500, delta_time) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * delta_time) internal_output.gases[/datum/gas/freon][MOLES] += scaled_production * 1.15 if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) @@ -367,7 +368,7 @@ radiation *= 2 heat_output *= 2.25 if(moderator_list[/datum/gas/bz]) - induce_hallucination(900, delta_time, force=TRUE) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * delta_time) internal_output.gases[/datum/gas/antinoblium][MOLES] += clamp(dirty_production_rate / 0.045, 0, 10) * delta_time if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) @@ -406,7 +407,7 @@ check_gravity_pulse(delta_time) - emit_rads() + radiation_pulse(src, max_range = 6, threshold = 0.3) /obj/machinery/atmospherics/components/unary/hypertorus/core/proc/evaporate_moderator(delta_time) // Don't evaporate if the reaction is dead diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm index d86b948459d..dbc09bf1748 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm @@ -556,29 +556,6 @@ qdel(src) -/** - * Induce hallucinations in nearby humans. - * - * force will make hallucinations ignore meson protection. - */ -/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/induce_hallucination(strength, delta_time, force=FALSE) - for(var/mob/living/carbon/human/human in view(src, HALLUCINATION_HFR(heat_output))) - if(!force && istype(human.glasses, /obj/item/clothing/glasses/meson)) - continue - var/distance_root = sqrt(1 / max(1, get_dist(human, src))) - human.hallucination += strength * distance_root * delta_time - human.hallucination = clamp(human.hallucination, 0, 200) - -/** - * Emit radiation - */ -/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/emit_rads() - radiation_pulse( - src, - max_range = 6, - threshold = 0.3, - ) - /* * HFR cracking related procs */ @@ -650,4 +627,3 @@ ) spill_gases(cracked_part, moderator_internal, ratio = HYPERTORUS_STRONG_SPILL_INITIAL) return - diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index d3429be71ed..f813a252d62 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -25,7 +25,17 @@ //Variables declared to change how items in the launch bay are picked and launched. (Almost) all of these are changed in the ui_act proc //Some effect groups are choices, while other are booleans. This is because some effects can stack, while others dont (ex: you can stack explosion and quiet, but you cant stack ordered launch and random launch) /datum/centcom_podlauncher - var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /obj/effect/particle_effect/sparks, /obj/effect/pod_landingzone, /obj/effect/hallucination/simple/supplypod_selector, /obj/effect/hallucination/simple/dropoff_location)) + /// Static typecache of atoms we won't lift up, or pod or whatever. + var/static/list/ignored_atoms = typecacheof(list( + null, // I don't know why null is the first element of this typepache but it was there when I found it + /mob/dead, + /obj/effect/landmark, + /obj/docking_port, + /obj/effect/particle_effect/sparks, + /obj/effect/pod_landingzone, + /obj/effect/client_image_holder, + )) + var/turf/oldTurf //Keeps track of where the user was at if they use the "teleport to centcom" button, so they can go back var/client/holder //client of whoever is using this datum var/area/centcom/central_command_areas/supplypod/loading/bay //What bay we're using to launch shit from. @@ -46,8 +56,12 @@ var/list/orderedArea = list() //Contains an ordered list of turfs in an area (filled in the createOrderedArea() proc), read top-left to bottom-right. Used for the "ordered" launch mode (launchChoice = 1) var/list/turf/acceptableTurfs = list() //Contians a list of turfs (in the "bay" area on centcom) that have items that can be launched. Taken from orderedArea var/list/launchList = list() //Contains whatever is going to be put in the supplypod and fired. Taken from acceptableTurfs - var/obj/effect/hallucination/simple/supplypod_selector/selector //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1) - var/obj/effect/hallucination/simple/dropoff_location/indicator + + /// An effect used for showing where a reverse pod will land + var/obj/effect/client_image_holder/dropoff_location/indicator + /// An effect used for keeping track of what item is going to be launched next when in "ordered" mode (launchChoice = 1) + var/obj/effect/client_image_holder/supplypod_selector/selector + var/obj/structure/closet/supplypod/centcompod/temp_pod //The temporary pod that is modified by this datum, then cloned. The buildObject() clone of this pod is what is launched // Stuff needed to render the map var/map_name @@ -794,7 +808,7 @@ QDEL_NULL(temp_pod) //Delete the temp_pod QDEL_NULL(selector) //Delete the selector effect QDEL_NULL(indicator) - . = ..() + return ..() /datum/centcom_podlauncher/proc/supplypod_punish_log(list/whoDyin) var/podString = effectBurst ? "5 pods" : "a pod" @@ -874,7 +888,7 @@ GLOBAL_DATUM_INIT(podlauncher, /datum/centcom_podlauncher, new) temp_pod.reverse_dropoff_coords = list(target_turf.x, target_turf.y, target_turf.z) indicator.forceMove(target_turf) -/obj/effect/hallucination/simple/supplypod_selector +/obj/effect/client_image_holder/supplypod_selector // Shows which item will be taken next name = "Supply Selector (Only you can see this)" image_icon = 'icons/obj/supplypods_32x32.dmi' image_state = "selector" @@ -883,7 +897,7 @@ GLOBAL_DATUM_INIT(podlauncher, /datum/centcom_podlauncher, new) plane = ABOVE_GAME_PLANE alpha = 150 -/obj/effect/hallucination/simple/dropoff_location +/obj/effect/client_image_holder/dropoff_location // Shows where revese pods lands name = "Dropoff Location (Only you can see this)" image_icon = 'icons/obj/supplypods_32x32.dmi' image_state = "dropoff_indicator" diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index eaed1ec8e48..de4fbde3c26 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -334,51 +334,32 @@ desc = "An experimental suit of armor with sensitive detectors hooked up to the mind of the wearer, sending mind pulses that causes hallucinations around you." cooldown_message = span_danger("The connection is currently out of sync... Recalibrating.") emp_message = span_warning("You feel the backsurge of a mind pulse.") - var/range = 3 - -/obj/item/clothing/suit/armor/reactive/hallucinating/dropped(mob/user) - ..() - if(istype(user)) - REMOVE_TRAIT(user, TRAIT_MADNESS_IMMUNE, "reactive_hallucinating_armor") - -/obj/item/clothing/suit/armor/reactive/hallucinating/equipped(mob/user, slot) - ..() - if(slot_flags & slot) //Was equipped to a valid slot for this item? - ADD_TRAIT(user, TRAIT_MADNESS_IMMUNE, "reactive_hallucinating_armor") + clothing_traits = list(TRAIT_MADNESS_IMMUNE) /obj/item/clothing/suit/armor/reactive/hallucinating/cooldown_activation(mob/living/carbon/human/owner) var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread sparks.set_up(1, 1, src) sparks.start() - ..() + return ..() /obj/item/clothing/suit/armor/reactive/hallucinating/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) owner.visible_message(span_danger("[src] blocks [attack_text], sending out mental pulses!")) - hallucination_pulse(owner) + visible_hallucination_pulse( + center = get_turf(owner), + radius = 3, + hallucination_duration = 50 SECONDS, + hallucination_max_duration = 300 SECONDS, + ) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration return TRUE /obj/item/clothing/suit/armor/reactive/hallucinating/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) owner.visible_message(span_danger("[src] blocks [attack_text], but pulls a massive charge of mental energy into [owner] from the surrounding environment!")) - owner.hallucination += 25 - owner.hallucination = clamp(owner.hallucination, 0, 150) + owner.adjust_hallucinations_up_to(50 SECONDS, 300 SECONDS) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration return TRUE -/obj/item/clothing/suit/armor/reactive/hallucinating/proc/hallucination_pulse(mob/living/carbon/human/owner) - var/turf/location = get_turf(owner) - for(var/mob/living/carbon/human/near in view(location, range)) - // If they are immune to hallucinations. - if (HAS_TRAIT(near, TRAIT_MADNESS_IMMUNE) || (near.mind && HAS_TRAIT(near.mind, TRAIT_MADNESS_IMMUNE))) - continue - - // Everyone else gets hallucinations. - var/dist = sqrt(1 / max(1, get_dist(near, location))) - near.hallucination += 25 * dist - near.hallucination = clamp(near.hallucination, 0, 150) - //Bioscrambling - /obj/item/clothing/suit/armor/reactive/bioscrambling name = "reactive bioscrambling armor" desc = "An experimental suit of armor with sensitive detectors hooked up to a biohazard release valve. It scrambles the bodies of those around." diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index 65c1b79c0e6..d0f9b7d1f6a 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -144,8 +144,9 @@ Runs the event SIGNAL_HANDLER return CANCEL_RANDOM_EVENT -//Special admins setup -/datum/round_event_control/proc/admin_setup() +/// Any special things admins can do while triggering this event to "improve" it. +/// Return [ADMIN_CANCEL_EVENT] to stop the event from actually happening after all +/datum/round_event_control/proc/admin_setup(mob/admin) return /datum/round_event //NOTE: Times are measured in master controller ticks! diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm index df421c6faf7..48f16700380 100644 --- a/code/modules/events/false_alarm.dm +++ b/code/modules/events/false_alarm.dm @@ -7,7 +7,7 @@ category = EVENT_CATEGORY_BUREAUCRATIC description = "Fakes an event announcement." -/datum/round_event_control/falsealarm/admin_setup() +/datum/round_event_control/falsealarm/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index e55c6380045..aa16be5506b 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -17,7 +17,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 category = EVENT_CATEGORY_SPACE description = "The station passes through an immovable rod." -/datum/round_event_control/immovable_rod/admin_setup() +/datum/round_event_control/immovable_rod/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm index 0844194c5f6..d2ccc2d49e1 100644 --- a/code/modules/events/mass_hallucination.dm +++ b/code/modules/events/mass_hallucination.dm @@ -1,48 +1,135 @@ /datum/round_event_control/mass_hallucination name = "Mass Hallucination" + description = "All crewmembers start to hallucinate the same thing." typepath = /datum/round_event/mass_hallucination weight = 10 max_occurrences = 2 min_players = 1 category = EVENT_CATEGORY_HEALTH - description = "Multiple crewmembers start to hallucinate the same thing." + + /// For admins, what hallucination did we pick + var/admin_forced_hallucination + /// For admins, what arguments are we passing to said hallucination + var/list/admin_forced_args + +/datum/round_event_control/mass_hallucination/admin_setup(mob/admin) + if(!check_rights(R_FUN)) + return ADMIN_CANCEL_EVENT + + var/force = tgui_alert(usr, "Do you want to force a hallucination?", name, list("Yes", "No", "Cancel")) + if(force == "Cancel") + return ADMIN_CANCEL_EVENT + if(force != "Yes") + return + + var/force_what = tgui_alert(usr, "Generic hallucination or Custom configured delusion? (Delusions are those which make people appear as other mobs)", name, list("Hallucination", "Custom Delusion", "Cancel")) + switch(force_what) + if("Cancel") + return ADMIN_CANCEL_EVENT + + if("Hallucination") + var/chosen = select_hallucination_type(admin, "What hallucination should be forced for [name]?", name) + if(!chosen || !check_rights(R_FUN)) + return ADMIN_CANCEL_EVENT + + admin_forced_hallucination = chosen + + if("Custom Delusion") + var/list/chosen_args = create_delusion(admin) + if(!length(chosen_args) || !check_rights(R_FUN)) + return ADMIN_CANCEL_EVENT + + admin_forced_hallucination = chosen_args[1] + admin_forced_args = chosen_args.Copy(3) /datum/round_event/mass_hallucination fakeable = FALSE /datum/round_event/mass_hallucination/start() - switch(rand(1,4)) - if(1) //same sound for everyone - var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new /datum/hallucination/sounds(C, TRUE, sound) - if(2) - var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) - if(3) - var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new /datum/hallucination/stationmessage(C, TRUE, stationmessage) - if(4 to 6) - var/picked_hallucination = pick( /datum/hallucination/bolts, - /datum/hallucination/chat, - /datum/hallucination/message, - /datum/hallucination/bolts, - /datum/hallucination/fake_flood, - /datum/hallucination/battle, - /datum/hallucination/fire, - /datum/hallucination/self_delusion, - /datum/hallucination/death, - /datum/hallucination/delusion, - /datum/hallucination/oh_yeah) - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new picked_hallucination(C, TRUE) + var/datum/round_event_control/mass_hallucination/our_controller = control + + var/picked_hallucination = our_controller?.admin_forced_hallucination + var/list/other_args = our_controller?.admin_forced_args + + if(!picked_hallucination) + var/category_to_pick_from = rand(1, 10) + switch(category_to_pick_from) + if(1) + // Send the same sound to everyone + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/fake_sound/normal) + + if(2) + // Send the same sound to everyone, but weird + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/fake_sound/weird) + + if(3) + // Send the same message to everyone + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/station_message) + + if(4) + // Send the same delusion to everyone, but... + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset) + // The delusion will affect everyone BUT the hallucinator. + other_args = list( + duration = 30 SECONDS, + skip_nearby = FALSE, + affects_us = FALSE, + affects_others = TRUE, + play_wabbajack = FALSE, + ) + + if(5) + // Send the same delusion to everyone, but... + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset) + // The delusion will affect only the hallucinator. + other_args = list( + duration = 45 SECONDS, + skip_nearby = FALSE, + affects_us = TRUE, + affects_others = FALSE, + play_wabbajack = TRUE, + ) + + if(6 to 10) + // Send the same generic hallucination type to everyone + var/static/list/generic_hallucinations = list( + /datum/hallucination/bolts, + /datum/hallucination/chat, + /datum/hallucination/death, + /datum/hallucination/fake_flood, + /datum/hallucination/fire, + /datum/hallucination/message, + /datum/hallucination/oh_yeah, + /datum/hallucination/xeno_attack, + ) + + picked_hallucination = pick(generic_hallucinations) + + if(!picked_hallucination) + CRASH("[type] couldn't find a hallucination to play. (Got: [picked_hallucination], Picked category: [category_to_pick_from])") + + var/list/hallucination_args = list(picked_hallucination, "mass hallucination") + if(islist(other_args)) + hallucination_args += other_args + + // We'll only hallucinate for carbons now, even though livings can hallucinate just fine in most cases. + for(var/mob/living/carbon/hallucinating as anything in GLOB.carbon_list) + // If they're on centcom, skip them entirely. + if(is_centcom_level(hallucinating.z)) + continue + // We can skip dead carbons as well + if(hallucinating.stat == DEAD) + continue + // Hallucinations can have side effects on mobs, like being stunned, + // so we'll play the hallucination to clientless mobs as well. + // Unless the mob is off the station z-level. It's unlikely anyone will notice. + if(hallucinating.z != 0 && !is_station_level(hallucinating.z) && !hallucinating.client) + continue + + // Not using the wrapper here because we already have a list / arglist + hallucinating._cause_hallucination(hallucination_args) + +/datum/round_event/mass_hallucination/end() + var/datum/round_event_control/mass_hallucination/our_controller = control + our_controller.admin_forced_hallucination = null + our_controller.admin_forced_args = null diff --git a/code/modules/events/wizard/departmentrevolt.dm b/code/modules/events/wizard/departmentrevolt.dm index 478d2109670..4fedb8e784e 100644 --- a/code/modules/events/wizard/departmentrevolt.dm +++ b/code/modules/events/wizard/departmentrevolt.dm @@ -13,7 +13,7 @@ ///admin choice on whether this nation will have objectives to attack other nations, default true for !fun! var/dangerous_nation = TRUE -/datum/round_event_control/wizard/deprevolt/admin_setup() +/datum/round_event_control/wizard/deprevolt/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/events/wizard/madness.dm b/code/modules/events/wizard/madness.dm index b960b074678..bcf40be2883 100644 --- a/code/modules/events/wizard/madness.dm +++ b/code/modules/events/wizard/madness.dm @@ -7,7 +7,7 @@ var/forced_secret -/datum/round_event_control/wizard/madness/admin_setup() +/datum/round_event_control/wizard/madness/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/hallucination/HUD.dm b/code/modules/hallucination/HUD.dm deleted file mode 100644 index 8e7bf445d70..00000000000 --- a/code/modules/hallucination/HUD.dm +++ /dev/null @@ -1,156 +0,0 @@ -/* HUD Hallucinations - * - * Contains: - * Fake Alerts - * Health Alerts - * Health Doll Damage - */ - -/datum/hallucination/hudscrew - -/datum/hallucination/hudscrew/New(mob/living/carbon/C, forced = TRUE, screwyhud_type) - set waitfor = FALSE - ..() - //Screwy HUD - var/chosen_screwyhud = screwyhud_type - if(!chosen_screwyhud) - chosen_screwyhud = pick(SCREWYHUD_CRIT,SCREWYHUD_DEAD,SCREWYHUD_HEALTHY) - target.set_screwyhud(chosen_screwyhud) - feedback_details += "Type: [target.hal_screwyhud]" - QDEL_IN(src, rand(100, 250)) - -/datum/hallucination/hudscrew/Destroy() - target?.set_screwyhud(SCREWYHUD_NONE) - return ..() - -/datum/hallucination/fake_alert - var/alert_type - -/datum/hallucination/fake_alert/New(mob/living/carbon/C, forced = TRUE, specific, duration = 150) - set waitfor = FALSE - ..() - alert_type = pick( - ALERT_NOT_ENOUGH_OXYGEN, - ALERT_NOT_ENOUGH_PLASMA, - ALERT_NOT_ENOUGH_CO2, - ALERT_TOO_MUCH_OXYGEN, - ALERT_TOO_MUCH_CO2, - ALERT_TOO_MUCH_PLASMA, - ALERT_NUTRITION, - ALERT_GRAVITY, - ALERT_FIRE, - ALERT_TEMPERATURE_HOT, - ALERT_TEMPERATURE_COLD, - ALERT_PRESSURE, - ALERT_NEW_LAW, - ALERT_LOCKED, - ALERT_HACKED, - ALERT_CHARGE, - ) - - if(specific) - alert_type = specific - feedback_details += "Type: [alert_type]" - switch(alert_type) - if(ALERT_NOT_ENOUGH_OXYGEN) - target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_oxy, override = TRUE) - if(ALERT_NOT_ENOUGH_PLASMA) - target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_plas, override = TRUE) - if(ALERT_NOT_ENOUGH_CO2) - target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_co2, override = TRUE) - if(ALERT_TOO_MUCH_OXYGEN) - target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_oxy, override = TRUE) - if(ALERT_TOO_MUCH_CO2) - target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_co2, override = TRUE) - if(ALERT_TOO_MUCH_PLASMA) - target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_plas, override = TRUE) - if(ALERT_NUTRITION) - if(prob(50)) - target.throw_alert(alert_type, /atom/movable/screen/alert/fat, override = TRUE) - else - target.throw_alert(alert_type, /atom/movable/screen/alert/starving, override = TRUE) - if(ALERT_GRAVITY) - target.throw_alert(alert_type, /atom/movable/screen/alert/weightless, override = TRUE) - if(ALERT_FIRE) - target.throw_alert(alert_type, /atom/movable/screen/alert/fire, override = TRUE) - if(ALERT_TEMPERATURE_HOT) - alert_type = "temp" - target.throw_alert(alert_type, /atom/movable/screen/alert/hot, 3, override = TRUE) - if(ALERT_TEMPERATURE_COLD) - alert_type = "temp" - target.throw_alert(alert_type, /atom/movable/screen/alert/cold, 3, override = TRUE) - if(ALERT_PRESSURE) - if(prob(50)) - target.throw_alert(alert_type, /atom/movable/screen/alert/highpressure, 2, override = TRUE) - else - target.throw_alert(alert_type, /atom/movable/screen/alert/lowpressure, 2, override = TRUE) - //BEEP BOOP I AM A ROBOT - if(ALERT_NEW_LAW) - target.throw_alert(alert_type, /atom/movable/screen/alert/newlaw, override = TRUE) - if(ALERT_LOCKED) - target.throw_alert(alert_type, /atom/movable/screen/alert/locked, override = TRUE) - if(ALERT_HACKED) - target.throw_alert(alert_type, /atom/movable/screen/alert/hacked, override = TRUE) - if(ALERT_CHARGE) - target.throw_alert(alert_type, /atom/movable/screen/alert/emptycell, override = TRUE) - - addtimer(CALLBACK(src, .proc/cleanup), duration) - -/datum/hallucination/fake_alert/proc/cleanup() - target.clear_alert(alert_type, clear_override = TRUE) - qdel(src) - -///Causes the target to see incorrect health damages on the healthdoll -/datum/hallucination/fake_health_doll - var/timer_id = null - -///Creates a specified doll hallucination, or picks one randomly -/datum/hallucination/fake_health_doll/New(mob/living/carbon/human/human_mob, forced = TRUE, specific_limb, severity, duration = 500) - . = ..() - if(!specific_limb) - specific_limb = pick(list(SCREWYDOLL_HEAD, SCREWYDOLL_CHEST, SCREWYDOLL_L_ARM, SCREWYDOLL_R_ARM, SCREWYDOLL_L_LEG, SCREWYDOLL_R_LEG)) - if(!severity) - severity = rand(1, 5) - LAZYSET(human_mob.hal_screwydoll, specific_limb, severity) - human_mob.update_health_hud() - - timer_id = addtimer(CALLBACK(src, .proc/cleanup), duration, TIMER_STOPPABLE) - -///Increments the severity of the damage seen on the doll -/datum/hallucination/fake_health_doll/proc/increment_fake_damage() - if(!ishuman(target)) - stack_trace("Somehow [target] managed to get a fake health doll hallucination, while not being a human mob.") - var/mob/living/carbon/human/human_mob = target - for(var/entry in human_mob.hal_screwydoll) - human_mob.hal_screwydoll[entry] = clamp(human_mob.hal_screwydoll[entry]+1, 1, 5) - human_mob.update_health_hud() - -///Adds a fake limb to the hallucination datum effect -/datum/hallucination/fake_health_doll/proc/add_fake_limb(specific_limb, severity) - if(!specific_limb) - specific_limb = pick(list(SCREWYDOLL_HEAD, SCREWYDOLL_CHEST, SCREWYDOLL_L_ARM, SCREWYDOLL_R_ARM, SCREWYDOLL_L_LEG, SCREWYDOLL_R_LEG)) - if(!severity) - severity = rand(1, 5) - var/mob/living/carbon/human/human_mob = target - LAZYSET(human_mob.hal_screwydoll, specific_limb, severity) - target.update_health_hud() - -/datum/hallucination/fake_health_doll/target_deleting() - if(isnull(timer_id)) - return - deltimer(timer_id) - timer_id = null - ..() - -///Cleans up the hallucinations - this deletes any overlap, but that shouldn't happen. -/datum/hallucination/fake_health_doll/proc/cleanup() - qdel(src) - -//So that the associated addition proc cleans it up correctly -/datum/hallucination/fake_health_doll/Destroy() - if(!ishuman(target)) - stack_trace("Somehow [target] managed to get a fake health doll hallucination, while not being a human mob.") - var/mob/living/carbon/human/human_mob = target - LAZYNULL(human_mob.hal_screwydoll) - human_mob.update_health_hud() - return ..() diff --git a/code/modules/hallucination/_hallucination.dm b/code/modules/hallucination/_hallucination.dm index 8f54b0b2af8..90622ed430f 100644 --- a/code/modules/hallucination/_hallucination.dm +++ b/code/modules/hallucination/_hallucination.dm @@ -1,162 +1,227 @@ -GLOBAL_LIST_INIT(hallucination_list, list( - /datum/hallucination/chat = 100, - /datum/hallucination/message = 60, - /datum/hallucination/sounds = 50, - /datum/hallucination/battle = 20, - /datum/hallucination/dangerflash = 15, - /datum/hallucination/hudscrew = 12, - /datum/hallucination/fake_health_doll = 12, - /datum/hallucination/fake_alert = 12, - /datum/hallucination/weird_sounds = 8, - /datum/hallucination/stationmessage = 7, - /datum/hallucination/fake_flood = 7, - /datum/hallucination/stray_bullet = 7, - /datum/hallucination/bolts = 7, - /datum/hallucination/items_other = 7, - /datum/hallucination/husks = 7, - /datum/hallucination/items = 4, - /datum/hallucination/fire = 3, - /datum/hallucination/self_delusion = 2, - /datum/hallucination/delusion = 2, - /datum/hallucination/shock = 1, - /datum/hallucination/death = 1, - /datum/hallucination/oh_yeah = 1 - )) - - -/mob/living/carbon/proc/handle_hallucinations(delta_time, times_fired) - if(!hallucination) - return - - hallucination = max(hallucination - (0.5 * delta_time), 0) - if(world.time < next_hallucination) - return - - var/halpick = pick_weight(GLOB.hallucination_list) - new halpick(src, FALSE) - - next_hallucination = world.time + rand(100, 600) - -/mob/living/carbon/proc/set_screwyhud(hud_type) - hal_screwyhud = hud_type - update_health_hud() - +/** + * # Hallucination datum. + * + * Handles effects of a hallucination on a living mob. + * Created and triggered via the [cause hallucination proc][/mob/living/proc/cause_hallucination]. + * + * See also: [the hallucination status effect][/datum/status_effect/hallucination]. + */ /datum/hallucination - var/natural = TRUE - var/mob/living/carbon/target - var/feedback_details //extra info for investigate + /// What is this hallucination's weight in the random hallucination pool? + var/random_hallucination_weight = 0 + /// Who's our next highest abstract parent type? + var/abstract_hallucination_parent = /datum/hallucination + /// Extra info about the hallucination displayed in the log. + var/feedback_details = "" + /// The mob we're targeting with the hallucination. + var/mob/living/hallucinator -/datum/hallucination/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - target = C - natural = !forced +/datum/hallucination/New(mob/living/hallucinator) + if(!isliving(hallucinator)) + stack_trace("[type] was created without a hallucinating mob.") + qdel(src) + return - // Cancel early if the target is deleted - RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/target_deleting) + src.hallucinator = hallucinator + RegisterSignal(hallucinator, COMSIG_PARENT_QDELETING, .proc/target_deleting) + GLOB.all_ongoing_hallucinations += src +/// Signal proc for [COMSIG_PARENT_QDELETING], if the mob hallucinating us is deletes, we should delete too. /datum/hallucination/proc/target_deleting() SIGNAL_HANDLER qdel(src) -/datum/hallucination/proc/wake_and_restore() - target.set_screwyhud(SCREWYHUD_NONE) - target.SetSleeping(0) +/// Starts the hallucination. +/datum/hallucination/proc/start() + SHOULD_CALL_PARENT(FALSE) + stack_trace("[type] didn't implement any hallucination effects in start.") /datum/hallucination/Destroy() - target.investigate_log("was afflicted with a hallucination of type [type] by [natural?"hallucination status":"an external source"]. [feedback_details]", INVESTIGATE_HALLUCINATIONS) + if(hallucinator) + UnregisterSignal(hallucinator, COMSIG_PARENT_QDELETING) + hallucinator = null - if (target) - UnregisterSignal(target, COMSIG_PARENT_QDELETING) - - target = null + GLOB.all_ongoing_hallucinations -= src return ..() -//Returns a random turf in a ring around the target mob, useful for sound hallucinations +/// Returns a random turf in a ring around the hallucinator mob. +/// Useful for sound hallucinations. /datum/hallucination/proc/random_far_turf() - var/x_based = prob(50) - var/first_offset = pick(-8,-7,-6,-5,5,6,7,8) - var/second_offset = rand(-8,8) - var/x_off - var/y_off - if(x_based) - x_off = first_offset - y_off = second_offset + var/first_offset = pick(-8, -7, -6, -5, 5, 6, 7, 8) + var/second_offset = rand(-8, 8) + var/x_offset + var/y_offset + if(prob(50)) + x_offset = first_offset + y_offset = second_offset else - y_off = first_offset - x_off = second_offset - var/turf/T = locate(target.x + x_off, target.y + y_off, target.z) - return T + x_offset = second_offset + y_offset = first_offset -/obj/effect/hallucination + return locate(hallucinator.x + x_offset, hallucinator.y + y_offset, hallucinator.z) + +/// Gets a random non-security member of the crew that is at least 8 tiles away. +/datum/hallucination/proc/random_non_sec_crewmember() + var/list/possible_fakes = list() + for(var/datum/mind/possible_fake as anything in get_crewmember_minds()) + // Sec won't make sense. (Neither will cap but we'll just let it slide) + if(possible_fake.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) + continue + // Look for minds on the manifest in control of humans + var/mob/living/carbon/human/fake_body = possible_fake.current + if(!istype(fake_body) || fake_body == hallucinator) + continue + // This also won't make sense in most cases + if(get_dist(fake_body, hallucinator) < 8) + continue + possible_fakes += fake_body + + return length(possible_fakes) ? pick(possible_fakes) : null + +/** + * Simple effect that holds an image + * to be shown to one or multiple clients only. + * + * Pass a list of mobs in initialize() that corresponds to all mobs that can see it. + */ +/obj/effect/client_image_holder invisibility = INVISIBILITY_OBSERVER anchored = TRUE - var/mob/living/carbon/target = null -/obj/effect/hallucination/simple - var/image_icon = 'icons/mob/nonhuman-player/alien.dmi' - var/image_state = "alienh_pounce" - var/px = 0 - var/py = 0 - var/col_mod = null - var/image/current_image = null + /// A list of mobs which can see us. + var/list/mob/who_sees_us + /// The created image, what we look like. + var/image/shown_image + /// The icon file the image uses. If null, we have no image + var/image_icon + /// The icon state the image uses + var/image_state + /// The x pixel offset of the image + var/image_pixel_x = 0 + /// The y pixel offset of the image + var/image_pixel_y = 0 + /// Optional, the color of the image + var/image_color + /// The layer of the image var/image_layer = MOB_LAYER + /// The plane of the image var/image_plane = GAME_PLANE - var/active = TRUE //qdelery -/obj/effect/hallucination/singularity_pull() - return - -/obj/effect/hallucination/singularity_act() - return - -/obj/effect/hallucination/simple/Initialize(mapload, mob/living/carbon/T) +/obj/effect/client_image_holder/Initialize(mapload, list/mobs_which_see_us) . = ..() - if(!T) - stack_trace("A hallucination was created with no target") + if(isnull(mobs_which_see_us)) + stack_trace("Client image holder was created with no mobs to see it.") return INITIALIZE_HINT_QDEL - target = T - current_image = GetImage() - if(target.client) - target.client.images |= current_image -/obj/effect/hallucination/simple/proc/GetImage() - var/image/I = image(image_icon,src,image_state,image_layer,dir=src.dir) - I.plane = image_plane - I.pixel_x = px - I.pixel_y = py - if(col_mod) - I.color = col_mod - return I + shown_image = generate_image() -/obj/effect/hallucination/simple/proc/Show(update=1) - if(active) - if(target.client) - target.client.images.Remove(current_image) - if(update) - current_image = GetImage() - if(target.client) - target.client.images |= current_image + if(!islist(mobs_which_see_us)) + mobs_which_see_us = list(mobs_which_see_us) -/obj/effect/hallucination/simple/update_icon(updates=ALL, new_state, new_icon, new_px=0, new_py=0) - image_state = new_state - if(new_icon) - image_icon = new_icon - else - image_icon = initial(image_icon) - px = new_px - py = new_py + who_sees_us = list() + for(var/mob/seer as anything in mobs_which_see_us) + RegisterSignal(seer, COMSIG_MOB_LOGIN, .proc/show_image_to) + RegisterSignal(seer, COMSIG_PARENT_QDELETING, .proc/remove_seer) + who_sees_us += seer + show_image_to(seer) + +/obj/effect/client_image_holder/Destroy(force) + if(shown_image) + for(var/mob/seer as anything in who_sees_us) + remove_seer(seer) + shown_image = null + + who_sees_us.Cut() // probably not needed but who knows + return ..() + +/// Signal proc to clean up references if people who see us are deleted. +/obj/effect/client_image_holder/proc/remove_seer(mob/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_MOB_LOGIN, COMSIG_PARENT_QDELETING)) + hide_image_from(source) + who_sees_us -= source + + // No reason to exist, anymore + if(!QDELETED(src) && !length(who_sees_us)) + qdel(src) + +/// Generates the image which we take on. +/obj/effect/client_image_holder/proc/generate_image() + var/image/created = image(image_icon, src, image_state, image_layer, dir = src.dir) + created.plane = image_plane + created.pixel_x = image_pixel_x + created.pixel_y = image_pixel_y + if(image_color) + created.color = image_color + return created + +/// Shows the image we generated to the passed mob +/obj/effect/client_image_holder/proc/show_image_to(mob/show_to) + SIGNAL_HANDLER + + show_to.client?.images |= shown_image + +/// Hides the image we generated from the passed mob +/obj/effect/client_image_holder/proc/hide_image_from(mob/hide_from) + SIGNAL_HANDLER + + hide_from.client?.images -= shown_image + +/// Simple helper for refreshing / showing the image to everyone in our list. +/obj/effect/client_image_holder/proc/regenerate_image() + for(var/mob/seer as anything in who_sees_us) + hide_image_from(seer) + + shown_image = generate_image() + + for(var/mob/seer as anything in who_sees_us) + show_image_to(seer) + +// Whenever we perform icon updates, regenerate our image +/obj/effect/client_image_holder/update_icon(updates = ALL) . = ..() - Show() + regenerate_image() -/obj/effect/hallucination/simple/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) +// If we move for some reason, regenerate our image +/obj/effect/client_image_holder/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) . = ..() if(!loc) return - Show() + regenerate_image() -/obj/effect/hallucination/simple/Destroy() - if(target.client) - target.client.images.Remove(current_image) - active = FALSE +/obj/effect/client_image_holder/singularity_pull() + return + +/obj/effect/client_image_holder/singularity_act() + return + +/** + * A client-side image effect tied to the existence of a hallucination. + */ +/obj/effect/client_image_holder/hallucination + invisibility = INVISIBILITY_OBSERVER + anchored = TRUE + /// The hallucination that created us. + var/datum/hallucination/parent + +/obj/effect/client_image_holder/hallucination/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) + . = ..() + if(!parent) + stack_trace("[type] was created without a parent hallucination.") + return INITIALIZE_HINT_QDEL + + RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/parent_deleting) + src.parent = parent + +/obj/effect/client_image_holder/hallucination/Destroy(force) + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + parent = null return ..() + +/// Signal proc for [COMSIG_PARENT_QDELETING], if our associated hallucination deletes, we should too +/obj/effect/client_image_holder/hallucination/proc/parent_deleting(datum/source) + SIGNAL_HANDLER + + qdel(src) diff --git a/code/modules/hallucination/airlock.dm b/code/modules/hallucination/airlock.dm deleted file mode 100644 index 950a267f75c..00000000000 --- a/code/modules/hallucination/airlock.dm +++ /dev/null @@ -1,90 +0,0 @@ -/* Airlock Hallucinations - * - * Contains: - * Nearby airlocks being bolted - * Nearby airlocks being unbolted - */ - -/datum/hallucination/bolts - var/list/airlocks_to_hit - var/list/locks - var/next_action = 0 - var/locking = TRUE - -/datum/hallucination/bolts/New(mob/living/carbon/C, forced, door_number) - set waitfor = FALSE - ..() - if(!door_number) - door_number = rand(0,4) //if 0 bolts all visible doors - var/count = 0 - feedback_details += "Door amount: [door_number]" - - for(var/obj/machinery/door/airlock/A in range(7, target)) - if(count>door_number && door_number>0) - break - if(!A.density) - continue - count++ - LAZYADD(airlocks_to_hit, A) - - if(!LAZYLEN(airlocks_to_hit)) //no valid airlocks in sight - qdel(src) - return - - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/bolts/process(delta_time) - next_action -= (delta_time * 10) - if (next_action > 0) - return - - if (locking) - var/atom/next_airlock = pop(airlocks_to_hit) - if (next_airlock) - var/obj/effect/hallucination/fake_door_lock/lock = new(get_turf(next_airlock)) - lock.target = target - lock.airlock = next_airlock - LAZYADD(locks, lock) - - if (!LAZYLEN(airlocks_to_hit)) - locking = FALSE - next_action = 10 SECONDS - return - else - var/obj/effect/hallucination/fake_door_lock/next_unlock = popleft(locks) - if (next_unlock) - next_unlock.unlock() - else - qdel(src) - return - - next_action = rand(4, 12) - -/datum/hallucination/bolts/Destroy() - . = ..() - QDEL_LIST(locks) - STOP_PROCESSING(SSfastprocess, src) - -/obj/effect/hallucination/fake_door_lock - layer = CLOSED_DOOR_LAYER + 1 //for Bump priority - plane = GAME_PLANE - var/image/bolt_light - var/obj/machinery/door/airlock/airlock - -/obj/effect/hallucination/fake_door_lock/proc/lock() - bolt_light = image(airlock.overlays_file, get_turf(airlock), "lights_bolts",layer=airlock.layer+0.1) - if(target.client) - target.client.images |= bolt_light - target.playsound_local(get_turf(airlock), 'sound/machines/boltsdown.ogg',30,0,3) - -/obj/effect/hallucination/fake_door_lock/proc/unlock() - if(target.client) - target.client.images.Remove(bolt_light) - target.playsound_local(get_turf(airlock), 'sound/machines/boltsup.ogg',30,0,3) - qdel(src) - -/obj/effect/hallucination/fake_door_lock/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(mover == target && airlock.density) - return FALSE - diff --git a/code/modules/hallucination/battle.dm b/code/modules/hallucination/battle.dm new file mode 100644 index 00000000000..f02e4a2019e --- /dev/null +++ b/code/modules/hallucination/battle.dm @@ -0,0 +1,166 @@ +/// Battle hallucination, makes it sound like a melee or gun battle is going on in the background. +/datum/hallucination/battle + abstract_hallucination_parent = /datum/hallucination/battle + random_hallucination_weight = 3 + +/// Subtype of battle hallucination for gun based battles, where it sounds like someone is being shot. +/datum/hallucination/battle/gun + abstract_hallucination_parent = /datum/hallucination/battle/gun + /// The lower end to how many shots we'll fire. + var/shots_to_fire_lower_range = 3 + /// The upper end to how many shots we'll fire. + var/shots_to_fire_upper_range = 6 + /// The sound effect we play when we "fire" a shot. + var/fire_sound = 'sound/weapons/gun/shotgun/shot.ogg' + /// The sound we make when our shot actually "hits" "someone". + var/hit_person_sound = 'sound/weapons/pierce.ogg' + /// The sound we make when our shot misses someone and "hits" a "wall". + var/hit_wall_sound = SFX_RICOCHET + /// The number of successful hits required to "down" the "someone" we're firing at. + var/number_of_hits_to_end = 2 + /// The probability chance we have to make our "hit" person fall down after we pass the number_of_hits_to_end. + var/chance_to_fall = 80 + +/datum/hallucination/battle/gun/start() + fire_loop(random_far_turf(), rand(shots_to_fire_lower_range, shots_to_fire_upper_range)) + return TRUE + +/// The main loop for gun based hallucinations. +/datum/hallucination/battle/gun/proc/fire_loop(turf/source, shots_left = 3, hits = 0) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + // We shoot our shot. + hallucinator.playsound_local(source, fire_sound, 25, TRUE) + + // Shortly after shooting our shot, it plays a hit (or miss) sound. + var/next_hit_sound = rand(0.5 SECONDS, 1 SECONDS) + if(prob(50)) + addtimer(CALLBACK(hallucinator, /mob/.proc/playsound_local, source, hit_person_sound, 25, TRUE), next_hit_sound) + hits++ + else + addtimer(CALLBACK(hallucinator, /mob/.proc/playsound_local, source, hit_wall_sound, 25, TRUE), next_hit_sound) + + // If we scored enough hits, we have a chance to knock them down and stop the hallucination early. + if(hits >= number_of_hits_to_end && prob(chance_to_fall)) + addtimer(CALLBACK(hallucinator, /mob/.proc/playsound_local, source, SFX_BODYFALL, 25, TRUE), next_hit_sound) + qdel(src) + + // Or, if we do have shots left, keep it going. + else if(--shots_left > 0) + addtimer(CALLBACK(src, .proc/fire_loop, source, shots_left, hits), rand(CLICK_CD_RANGE, CLICK_CD_RANGE + 6)) + + // Otherwise, if we have no shots left, stop the hallucination. + else + qdel(src) + + +/// Gun battle hallucination that sounds like disabler fire. +/datum/hallucination/battle/gun/disabler + shots_to_fire_lower_range = 5 + shots_to_fire_upper_range = 10 + fire_sound = 'sound/weapons/taser2.ogg' + hit_person_sound = 'sound/weapons/tap.ogg' + hit_wall_sound = 'sound/weapons/effects/searwall.ogg' + number_of_hits_to_end = 3 + chance_to_fall = 70 + +/// Gun battle hallucination that sounds like laser fire. +/datum/hallucination/battle/gun/laser + shots_to_fire_lower_range = 5 + shots_to_fire_upper_range = 10 + fire_sound = 'sound/weapons/laser.ogg' + hit_person_sound = 'sound/weapons/sear.ogg' + hit_wall_sound = 'sound/weapons/effects/searwall.ogg' + number_of_hits_to_end = 4 + chance_to_fall = 70 + +/// A hallucination of someone being hit with a stun prod, followed by cable cuffing. +/datum/hallucination/battle/stun_prod + +/datum/hallucination/battle/stun_prod/start() + var/turf/source = random_far_turf() + + hallucinator.playsound_local(source, 'sound/weapons/egloves.ogg', 40, TRUE) + hallucinator.playsound_local(source, SFX_BODYFALL, 25, TRUE) + addtimer(CALLBACK(src, .proc/fake_cuff, source), 2 SECONDS) + return TRUE + +/// Plays a fake cable-cuff sound and deletes the hallucination. +/datum/hallucination/battle/stun_prod/proc/fake_cuff(turf/source) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + hallucinator.playsound_local(source, 'sound/weapons/cablecuff.ogg', 15, TRUE) + qdel(src) + +/// A hallucination of someone being stun batonned, and subsequently harmbatonned. +/datum/hallucination/battle/harm_baton + +/datum/hallucination/battle/harm_baton/start() + var/turf/source = random_far_turf() + + hallucinator.playsound_local(source, 'sound/weapons/egloves.ogg', 40, TRUE) + hallucinator.playsound_local(source, SFX_BODYFALL, 25, TRUE) + + addtimer(CALLBACK(src, .proc/harmbaton_loop, source, rand(5, 12)), 2 SECONDS) + return TRUE + +/// The main sound loop for harmbatonning. +/datum/hallucination/battle/harm_baton/proc/harmbaton_loop(turf/source, hits_remaing = 5) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + hallucinator.playsound_local(source, SFX_SWING_HIT, 50, TRUE) + if(--hits_remaing <= 0) + qdel(src) + + else + addtimer(CALLBACK(src, .proc/harmbaton_loop, source, hits_remaing), rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 4)) + +/// A hallucination of someone unsheathing an energy sword, going to town, and sheathing it again. +/datum/hallucination/battle/e_sword + +/datum/hallucination/battle/e_sword/start() + var/turf/source = random_far_turf() + + hallucinator.playsound_local(source, 'sound/weapons/saberon.ogg', 15, 1) + addtimer(CALLBACK(src, .proc/stab_loop, source, rand(4, 8)), CLICK_CD_MELEE) + return TRUE + +/// The main sound loop of someone being esworded. +/datum/hallucination/battle/e_sword/proc/stab_loop(turf/source, stabs_remaining = 4) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + if(stabs_remaining >= 1) + hallucinator.playsound_local(source, 'sound/weapons/blade1.ogg', 50, TRUE) + + else + hallucinator.playsound_local(source, 'sound/weapons/saberoff.ogg', 15, TRUE) + qdel(src) + return + + if(stabs_remaining == 4) + hallucinator.playsound_local(source, SFX_BODYFALL, 25, TRUE) + + addtimer(CALLBACK(src, .proc/stab_loop, source, stabs_remaining - 1), rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 6)) + +/// A hallucination of a syndicate bomb ticking down. +/datum/hallucination/battle/bomb + +/datum/hallucination/battle/bomb/start() + addtimer(CALLBACK(src, .proc/fake_tick, random_far_turf(), rand(3, 11)), 1.5 SECONDS) + return TRUE + +/// The loop of the (fake) bomb ticking down. +/datum/hallucination/battle/bomb/proc/fake_tick(turf/source, ticks_remaining = 3) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + hallucinator.playsound_local(source, 'sound/items/timer.ogg', 25, FALSE) + if(--ticks_remaining <= 0) + qdel(src) + + else + addtimer(CALLBACK(src, .proc/fake_tick, source, ticks_remaining), 1.5 SECONDS) diff --git a/code/modules/hallucination/body.dm b/code/modules/hallucination/body.dm new file mode 100644 index 00000000000..9bb8a02a0e6 --- /dev/null +++ b/code/modules/hallucination/body.dm @@ -0,0 +1,56 @@ +/// Makes a random body appear and disappear quickly. +/datum/hallucination/body + abstract_hallucination_parent = /datum/hallucination/body + /// The file to make the body image from. + var/body_image_file + /// The icon state to make the body image form. + var/body_image_state + /// The actual image we made and showed show. + var/image/shown_body + +/datum/hallucination/body/start() + // This hallucination is purely visual, so we don't need to bother for clientless mobs + if(!hallucinator.client) + return FALSE + + var/list/possible_points = list() + for(var/turf/open/floor/open_floor in view(hallucinator)) + possible_points += open_floor + + if(!length(possible_points)) + return FALSE + + shown_body = make_body_image(pick(possible_points)) + + hallucinator.client?.images |= shown_body + QDEL_IN(src, rand(3 SECONDS, 5 SECONDS)) //Only seen for a brief moment. + return TRUE + +/datum/hallucination/body/Destroy() + hallucinator.client?.images -= shown_body + shown_body = null + return ..() + +/// Makes the image of the body to show at the location passed. +/datum/hallucination/body/proc/make_body_image(turf/location) + return image(body_image_file, location, body_image_state, TURF_LAYER) + +/datum/hallucination/body/husk + random_hallucination_weight = 4 + body_image_file = 'icons/mob/species/human/human.dmi' + body_image_state = "husk" + +/datum/hallucination/body/husk/sideways + random_hallucination_weight = 2 + +/datum/hallucination/body/husk/sideways/make_body_image(turf/location) + var/image/body = ..() + var/matrix/turn_matrix = matrix() + turn_matrix.Turn(90) + body.transform = turn_matrix + return body + +/datum/hallucination/body/alien + random_hallucination_weight = 1 + body_image_file = 'icons/mob/nonhuman-player/alien.dmi' + body_image_state = "alienother" diff --git a/code/modules/hallucination/bolted_airlocks.dm b/code/modules/hallucination/bolted_airlocks.dm new file mode 100644 index 00000000000..1cf5f03b967 --- /dev/null +++ b/code/modules/hallucination/bolted_airlocks.dm @@ -0,0 +1,127 @@ +/datum/hallucination/bolts + random_hallucination_weight = 7 + /// A list of weakrefs to airlocks we bolt down around us + var/list/datum/weakref/airlocks_to_hit + /// A list of weakrefs to fake lock hallucinations we've created + var/list/datum/weakref/locks + /// A number relating to the number of ssfastprocessing ticks (x 10) until the next action + var/next_action = 0 + /// Whether we're currently locking, or unlocking + var/locking = TRUE + +/datum/hallucination/bolts/start() + var/door_number = rand(0, 4) //if 0, we bolt all visible doors + feedback_details += "Door amount: [door_number]" + + for(var/obj/machinery/door/airlock/nearby_airlock in view(hallucinator)) + if(LAZYLEN(airlocks_to_hit) > door_number && door_number > 0) + break + if(!nearby_airlock.density) + continue + LAZYADD(airlocks_to_hit, WEAKREF(nearby_airlock)) + + if(!LAZYLEN(airlocks_to_hit)) // Not an airlock in sight + return FALSE + + START_PROCESSING(SSfastprocess, src) + return TRUE + +/datum/hallucination/bolts/process(delta_time) + if(QDELETED(src)) + return + + next_action -= (delta_time * 10) + if(next_action > 0) + return + + if(locking) + var/datum/weakref/next_airlock = pop(airlocks_to_hit) + var/obj/machinery/door/airlock/to_lock = next_airlock?.resolve() + if(to_lock) + var/obj/effect/client_image_holder/hallucination/fake_door_lock/lock = new(to_lock.loc, hallucinator, src, to_lock) + LAZYADD(locks, WEAKREF(lock)) + + if(!LAZYLEN(airlocks_to_hit)) + locking = FALSE + next_action = 100 + return + + else + var/datum/weakref/next_unlock = popleft(locks) + var/obj/effect/client_image_holder/hallucination/fake_door_lock/to_unlock = next_unlock?.resolve() + if(to_unlock) + to_unlock.unlock() + + else + // All unlocked, qdel time + qdel(src) + return + + next_action = rand(4, 12) + +/datum/hallucination/bolts/Destroy() + // Clean up any locks we happen to have remaining on qdel. + // Hypothetically this shouldn't delete anything. But just in case. + for(var/datum/weakref/leftover_lock_ref as anything in locks) + var/obj/effect/client_image_holder/hallucination/fake_door_lock/leftover_lock = leftover_lock_ref.resolve() + if(!QDELETED(leftover_lock)) + qdel(leftover_lock) + + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/effect/client_image_holder/hallucination/fake_door_lock + layer = CLOSED_DOOR_LAYER + 1 //for Bump priority + plane = GAME_PLANE + + /// The real airlock we're fake bolting down. + var/obj/machinery/door/airlock/airlock + +/obj/effect/client_image_holder/hallucination/fake_door_lock/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent, obj/machinery/door/airlock/airlock) + if(!airlock) + stack_trace("[type] was created somewhere without an associated airlock.") + return INITIALIZE_HINT_QDEL + + src.airlock = airlock + RegisterSignal(airlock, COMSIG_PARENT_QDELETING, .proc/on_airlock_deleted) + // We need to grab these for our image before we run our parent's parent initialize + src.image_icon = airlock.overlays_file + src.image_state = "lights_[AIRLOCK_LIGHT_BOLTS]" + + return ..() + +/obj/effect/client_image_holder/hallucination/fake_door_lock/Destroy(force) + UnregisterSignal(airlock, COMSIG_PARENT_QDELETING) + airlock = null + return ..() + +/obj/effect/client_image_holder/hallucination/fake_door_lock/generate_image() + var/image/created = ..() + created.layer = airlock.layer + 0.1 + return created + +/obj/effect/client_image_holder/hallucination/fake_door_lock/show_image_to(mob/show_to) + . = ..() + show_to.playsound_local(get_turf(src), 'sound/machines/boltsdown.ogg', 30, FALSE, 3) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/hide_image_from(mob/show_to) + . = ..() + show_to.playsound_local(get_turf(src), 'sound/machines/boltsup.ogg', 30, FALSE, 3) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/proc/on_airlock_deleted(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/proc/unlock() + for(var/mob/seer as anything in who_sees_us) + hide_image_from(seer) + + shown_image = null + qdel(src) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/CanAllowThrough(atom/movable/mover, border_dir) + if((mover in who_sees_us) && airlock.density) + return FALSE + + return ..() diff --git a/code/modules/hallucination/bubblegum_attack.dm b/code/modules/hallucination/bubblegum_attack.dm new file mode 100644 index 00000000000..52796c8dea6 --- /dev/null +++ b/code/modules/hallucination/bubblegum_attack.dm @@ -0,0 +1,99 @@ +/// Sends a fake bubblegum charging through a nearby wall to our target. +/datum/hallucination/oh_yeah + random_hallucination_weight = 1 + /// An image overlayed to the wall bubblegum comes out of, to look destroyed. + var/image/fake_broken_wall + /// An image put where bubblegum is expected to land, to mimic his charge "rune" icon. + var/image/fake_rune + /// if TRUE, we will also send one of the hallucination lines when we start. + var/haunt_them = FALSE + /// if haunt_them is TRUE, they will also be shown one of these lines when the hallucination occurs + var/static/list/hallucination_lines = BUBBLEGUM_HALLUCINATION_LINES + +/datum/hallucination/oh_yeah/New(mob/living/hallucinator, source = "an external source", haunt_them = FALSE) + src.haunt_them = haunt_them + return ..() + +/datum/hallucination/oh_yeah/Destroy() + if(fake_broken_wall) + hallucinator.client?.images -= fake_broken_wall + fake_broken_wall = null + if(fake_rune) + hallucinator.client?.images -= fake_rune + fake_rune = null + + return ..() + +/datum/hallucination/oh_yeah/start() + var/turf/closed/wall/wall_source = locate() in range(7, hallucinator) + if(!wall_source) + return FALSE + + feedback_details += "Source: ([wall_source.x], [wall_source.y], [wall_source.z])" + + var/turf/target_landing_turf = get_turf(hallucinator) + var/turf/target_landing_image_turf = get_step(target_landing_turf, SOUTHWEST) // The icon is 3x3, so we shift down+left + + if(hallucinator.client) + + fake_broken_wall = image('icons/turf/floors.dmi', wall_source, "plating", layer = TURF_LAYER) + fake_broken_wall.override = TRUE + fake_rune = image('icons/effects/96x96.dmi', target_landing_image_turf, "landing", layer = ABOVE_OPEN_TURF_LAYER) + + hallucinator.client?.images |= fake_broken_wall + hallucinator.client?.images |= fake_rune + + hallucinator.playsound_local(wall_source, 'sound/effects/meteorimpact.ogg', 150, TRUE) + + if(haunt_them) + to_chat(hallucinator, pick(hallucination_lines)) + + var/obj/effect/client_image_holder/hallucination/bubblegum/fake_bubbles = new(wall_source, hallucinator, src) + addtimer(CALLBACK(src, .proc/charge_loop, fake_bubbles, target_landing_turf), 1 SECONDS) + return TRUE + +/** + * Recursive function that operates as a "fake charge" of our effect towards the target turf. + */ +/datum/hallucination/oh_yeah/proc/charge_loop(obj/effect/client_image_holder/hallucination/bubblegum/fake_bubbles, turf/landing_turf) + if(QDELETED(src)) + return + + if(QDELETED(hallucinator) \ + || QDELETED(fake_bubbles) \ + || !landing_turf \ + || fake_bubbles.z != hallucinator.z \ + || fake_bubbles.z != landing_turf.z \ + ) + qdel(src) + return + + if(get_turf(fake_bubbles) == landing_turf || hallucinator.stat == DEAD) + QDEL_IN(src, 3 SECONDS) + return + + fake_bubbles.forceMove(get_step_towards(fake_bubbles, landing_turf)) + fake_bubbles.setDir(get_dir(fake_bubbles, landing_turf)) + hallucinator.playsound_local(get_turf(fake_bubbles), 'sound/effects/meteorimpact.ogg', 150, TRUE) + shake_camera(hallucinator, 2, 1) + + if(fake_bubbles.Adjacent(hallucinator)) + hallucinator.Paralyze(8 SECONDS) + hallucinator.adjustStaminaLoss(40) + step_away(hallucinator, fake_bubbles) + shake_camera(hallucinator, 4, 3) + hallucinator.visible_message( + span_warning("[hallucinator] jumps backwards, falling on the ground!"), + span_userdanger("[fake_bubbles] slams into you!"), + ) + QDEL_IN(src, 3 SECONDS) + + else + addtimer(CALLBACK(src, .proc/charge_loop, fake_bubbles, landing_turf), 0.2 SECONDS) + +/// Fake bubblegum hallucination effect for the oh yeah hallucination +/obj/effect/client_image_holder/hallucination/bubblegum + name = "Bubblegum" + image_icon = 'icons/mob/simple/lavaland/96x96megafauna.dmi' + image_state = "bubblegum" + image_pixel_x = -32 diff --git a/code/modules/hallucination/chat.dm b/code/modules/hallucination/chat.dm deleted file mode 100644 index 0e0bf0ed457..00000000000 --- a/code/modules/hallucination/chat.dm +++ /dev/null @@ -1,169 +0,0 @@ -/* Chat Hallucinations - * - * Contains: - * Radio messages (";Someone recall the shuttle!", ";Set John Doe to arrest!") - * Speak messages ("Get out!", "Did you hear that?") - * Action messages ("You feel a tiny prick!", "John Doe puts the cryptographic sequencer into their backpack.") - * Station messages ("The Emergency Shuttle has docked with the station.", "Hostile runtimes detected in all station systems") - */ - -#define HAL_LINES_FILE "hallucination.json" - -/datum/hallucination/chat - -/datum/hallucination/chat/New(mob/living/carbon/C, forced = TRUE, force_radio, specific_message) - set waitfor = FALSE - ..() - var/target_name = target.first_name() - var/speak_messages = list("[pick_list_replacements(HAL_LINES_FILE, "suspicion")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "conversation")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "greetings")][target.first_name()]!",\ - "[pick_list_replacements(HAL_LINES_FILE, "getout")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "weird")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "didyouhearthat")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "doubt")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "aggressive")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "help")]!!",\ - "[pick_list_replacements(HAL_LINES_FILE, "escape")]",\ - "I'm infected, [pick_list_replacements(HAL_LINES_FILE, "infection_advice")]!") - - var/radio_messages = list("[pick_list_replacements(HAL_LINES_FILE, "people")] is [pick_list_replacements(HAL_LINES_FILE, "accusations")]!",\ - "Help!",\ - "[pick_list_replacements(HAL_LINES_FILE, "threat")] in [pick_list_replacements(HAL_LINES_FILE, "location")][prob(50)?"!":"!!"]",\ - "[pick("Where's [target.first_name()]?", "Set [target.first_name()] to arrest!")]",\ - "[pick("C","Ai, c","Someone c","Rec")]all the shuttle!",\ - "AI [pick("rogue", "is dead")]!!") - - var/mob/living/carbon/person = null - var/datum/language/understood_language = target.get_random_understood_language() - for(var/mob/living/carbon/H in view(target)) - if(H == target) - continue - if(!person) - person = H - else - if(get_dist(target,H)[other] puts the [pick(\ - "revolver","energy sword","cryptographic sequencer","power sink","energy bow",\ - "hybrid taser","stun baton","flash","syringe gun","circular saw","tank transfer valve",\ - "ritual dagger","spellbook",\ - "Codex Cicatrix", "Living Heart",\ - "pulse rifle","captain's spare ID","hand teleporter","hypospray","antique laser gun","X-01 MultiPhase Energy Gun","station's blueprints"\ - )] into [equipped_backpack].") - - message_pool.Add("[other] [pick("sneezes","coughs")].") - - message_pool.Add(span_notice("You hear something squeezing through the ducts..."), \ - span_notice("Your [pick("arm", "leg", "back", "head")] itches."),\ - span_warning("You feel [pick("hot","cold","dry","wet","woozy","faint")]."), - span_warning("Your stomach rumbles."), - span_warning("Your head hurts."), - span_warning("You hear a faint buzz in your head."), - "[target] sneezes.") - if(prob(10)) - message_pool.Add(span_warning("Behind you."),\ - span_warning("You hear a faint laughter."), - span_warning("You see something move."), - span_warning("You hear skittering on the ceiling."), - span_warning("You see an inhumanly tall silhouette moving in the distance.")) - if(prob(10)) - message_pool.Add("[pick_list_replacements(HAL_LINES_FILE, "advice")]") - var/chosen = pick(message_pool) - feedback_details += "Message: [chosen]" - to_chat(target, chosen) - qdel(src) - -/datum/hallucination/stationmessage - -/datum/hallucination/stationmessage/New(mob/living/carbon/C, forced = TRUE, message) - set waitfor = FALSE - ..() - if(!message) - message = pick("ratvar","shuttle dock","blob alert","malf ai","meteors","supermatter") - feedback_details += "Type: [message]" - switch(message) - if("blob alert") - to_chat(target, "

Biohazard Alert

") - to_chat(target, "

[span_alert("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK5]) - if("ratvar") - target.playsound_local(target, 'sound/machines/clockcult/ark_deathrattle.ogg', 50, FALSE, pressure_affected = FALSE) - target.playsound_local(target, 'sound/effects/clockcult_gateway_disrupted.ogg', 50, FALSE, pressure_affected = FALSE) - addtimer(CALLBACK( - target, - /mob/.proc/playsound_local, - target, - 'sound/effects/explosion_distant.ogg', - 50, - FALSE, - /* frequency = */ null, - /* falloff_exponential = */ null, - /* channel = */ null, - /* pressure_affected = */ FALSE - ), 27) - if("shuttle dock") - to_chat(target, "

Priority Announcement

") - to_chat(target, "

[span_alert("The Emergency Shuttle has docked with the station. You have 3 minutes to board the Emergency Shuttle.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLEDOCK]) - if("malf ai") //AI is doomsdaying! - to_chat(target, "

Anomaly Alert

") - to_chat(target, "

[span_alert("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_AIMALF]) - if("meteors") //Meteors inbound! - to_chat(target, "

Meteor Alert

") - to_chat(target, "

[span_alert("Meteors have been detected on collision course with the station.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_METEORS]) - if("supermatter") - SEND_SOUND(target, 'sound/magic/charge.ogg') - to_chat(target, span_boldannounce("You feel reality distort for a moment...")) diff --git a/code/modules/hallucination/death.dm b/code/modules/hallucination/death.dm deleted file mode 100644 index 744ff878a56..00000000000 --- a/code/modules/hallucination/death.dm +++ /dev/null @@ -1,34 +0,0 @@ -/datum/hallucination/death - -/datum/hallucination/death/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - target.set_screwyhud(SCREWYHUD_DEAD) - target.Paralyze(300) - target.silent += 10 - to_chat(target, span_deadsay("[target.real_name] has died at [get_area_name(target)].")) - - var/delay = 0 - - if(prob(50)) - var/mob/fakemob - var/list/dead_people = list() - for(var/mob/dead/observer/G in GLOB.player_list) - dead_people += G - if(LAZYLEN(dead_people)) - fakemob = pick(dead_people) - else - fakemob = target //ever been so lonely you had to haunt yourself? - if(fakemob) - delay = rand(20, 50) - addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, target, "DEAD: [fakemob.name] says, \"[pick("rip","why did i just drop dead?","hey [target.first_name()]","git gud","you too?","is the AI rogue?",\ - "i[prob(50)?" fucking":""] hate [pick("blood cult", "clock cult", "revenants", "this round","this","myself","admins","you")]")]\""), delay) - - addtimer(CALLBACK(src, .proc/cleanup), delay + rand(70, 90)) - -/datum/hallucination/death/proc/cleanup() - if (target) - target.set_screwyhud(SCREWYHUD_NONE) - target.SetParalyzed(0) - target.silent = FALSE - qdel(src) diff --git a/code/modules/hallucination/delusions.dm b/code/modules/hallucination/delusions.dm new file mode 100644 index 00000000000..d09745fdb15 --- /dev/null +++ b/code/modules/hallucination/delusions.dm @@ -0,0 +1,177 @@ +/// A hallucination that makes us and (possibly) other people look like something else. +/datum/hallucination/delusion + abstract_hallucination_parent = /datum/hallucination/delusion + + /// The duration of the delusions + var/duration = 30 SECONDS + + /// If TRUE, this delusion affects us + var/affects_us = TRUE + /// If TRUE, this hallucination affects all humans in existence + var/affects_others = FALSE + /// If TRUE, people in view of our hallcuinator won't be affected (requires affects_others) + var/skip_nearby = FALSE + /// If TRUE, we will play the wabbajack sound effect to the hallucinator + var/play_wabbajack = FALSE + + /// The file the delusion image is made from + var/delusion_icon_file + /// The icon state of the delusion image + var/delusion_icon_state + /// The name of the delusion image + var/delusion_name + + /// A list of all images we've made + var/list/image/delusions + +/datum/hallucination/delusion/New( + mob/living/hallucinator, + duration = 30 SECONDS, + affects_us = TRUE, + affects_others = FALSE, + skip_nearby = TRUE, + play_wabbajack = FALSE, +) + + src.duration = duration + src.affects_us = affects_us + src.affects_others = affects_others + src.skip_nearby = skip_nearby + src.play_wabbajack = play_wabbajack + return ..() + +/datum/hallucination/delusion/Destroy() + if(!QDELETED(hallucinator)) + for(var/image/to_remove as anything in delusions) + hallucinator.client?.images -= to_remove + + return ..() + +/datum/hallucination/delusion/start() + if(!hallucinator.client || !delusion_icon_file) + return FALSE + + feedback_details += "Delusion: [delusion_name]" + + var/list/mob/living/carbon/human/funny_looking_mobs = list() + + // The delusion includes others - all humans + if(affects_others) + funny_looking_mobs |= GLOB.human_list.Copy() + + // The delusion includes us - we might be in it already, we might not + if(affects_us) + funny_looking_mobs |= hallucinator + + // The delusion should not inlude us + else + funny_looking_mobs -= hallucinator + + // The delusion shouldn not include anyone in view of us + if(skip_nearby) + for(var/mob/living/carbon/human/nearby_human in view(hallucinator)) + if(nearby_human == hallucinator) // Already handled by affects_us + continue + funny_looking_mobs -= nearby_human + + for(var/mob/living/carbon/human/found_human in funny_looking_mobs) + var/image/funny_image = make_delusion_image(found_human) + LAZYADD(delusions, funny_image) + hallucinator.client.images |= funny_image + + if(play_wabbajack) + to_chat(hallucinator, span_hear("...wabbajack...wabbajack...")) + hallucinator.playsound_local(get_turf(hallucinator), 'sound/magic/staff_change.ogg', 50, TRUE) + + if(duration > 0) + QDEL_IN(src, duration) + return TRUE + +/datum/hallucination/delusion/proc/make_delusion_image(mob/over_who) + var/image/funny_image = image(delusion_icon_file, over_who, delusion_icon_state) + funny_image.name = delusion_name + funny_image.override = TRUE + return funny_image + +/// Used for making custom delusions. +/datum/hallucination/delusion/custom + random_hallucination_weight = 0 + +/datum/hallucination/delusion/custom/New( + mob/living/hallucinator, + duration = 30 SECONDS, + affects_us = TRUE, + affects_others = FALSE, + skip_nearby = FALSE, + play_wabbajack = FALSE, + custom_icon_file, + custom_icon_state, + custom_name, +) + + if(!custom_icon_file || !custom_icon_state) + stack_trace("Custom delusion hallucination was created without any custom icon information passed.") + + src.delusion_icon_file = custom_icon_file + src.delusion_icon_state = custom_icon_state + src.delusion_name = custom_name + + return ..() + +/datum/hallucination/delusion/preset + abstract_hallucination_parent = /datum/hallucination/delusion/preset + random_hallucination_weight = 2 + +/datum/hallucination/delusion/preset/nothing + delusion_icon_file = 'icons/effects/effects.dmi' + delusion_icon_state = "nothing" + delusion_name = "..." + +/datum/hallucination/delusion/preset/curse + delusion_icon_file = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + delusion_icon_state = "curseblob" + delusion_name = "???" + +/datum/hallucination/delusion/preset/monkey + delusion_icon_file = 'icons/mob/species/human/human.dmi' + delusion_icon_state = "monkey" + delusion_name = "monkey" + +/datum/hallucination/delusion/preset/monkey/start() + delusion_name += " ([rand(1, 999)])" + return ..() + +/datum/hallucination/delusion/preset/corgi + delusion_icon_file = 'icons/mob/simple/pets.dmi' + delusion_icon_state = "corgi" + delusion_name = "corgi" + +/datum/hallucination/delusion/preset/carp + delusion_icon_file = 'icons/mob/simple/carp.dmi' + delusion_icon_state = "carp" + delusion_name = "carp" + +/datum/hallucination/delusion/preset/skeleton + delusion_icon_file = 'icons/mob/species/human/human.dmi' + delusion_icon_state = "skeleton" + delusion_name = "skeleton" + +/datum/hallucination/delusion/preset/zombie + delusion_icon_file = 'icons/mob/species/human/human.dmi' + delusion_icon_state = "zombie" + delusion_name = "zombie" + +/datum/hallucination/delusion/preset/demon + delusion_icon_file = 'icons/mob/simple/mob.dmi' + delusion_icon_state = "daemon" + delusion_name = "demon" + +/datum/hallucination/delusion/preset/cyborg + play_wabbajack = TRUE + delusion_icon_file = 'icons/mob/silicon/robots.dmi' + delusion_icon_state = "robot" + delusion_name = "cyborg" + +/datum/hallucination/delusion/preset/cyborg/make_delusion_image(mob/over_who) + . = ..() + hallucinator.playsound_local(get_turf(over_who), 'sound/voice/liveagain.ogg', 75, TRUE) diff --git a/code/modules/hallucination/fake_alert.dm b/code/modules/hallucination/fake_alert.dm new file mode 100644 index 00000000000..56cecba0476 --- /dev/null +++ b/code/modules/hallucination/fake_alert.dm @@ -0,0 +1,107 @@ +/// Fake alert hallucination. Causes a fake alert to be thrown to the hallucinator. +/datum/hallucination/fake_alert + abstract_hallucination_parent = /datum/hallucination/fake_alert + random_hallucination_weight = 1 + + var/del_timer_id + /// The duration of the alert being thrown. + var/duration + /// The category of the fake alert + var/alert_category + /// The type of the fake alert. Can be a list, if you want it to draw from multiple types (randomly). + var/alert_type + /// Optional, the severity of the alert. + var/optional_severity + +/datum/hallucination/fake_alert/New(mob/living/hallucinator, duration = 15 SECONDS) + src.duration = duration + return ..() + +/datum/hallucination/fake_alert/Destroy() + if(del_timer_id) + deltimer(del_timer_id) + if(!QDELETED(hallucinator)) + hallucinator.clear_alert(alert_category, clear_override = TRUE) + return ..() + +/datum/hallucination/fake_alert/start() + var/picked_type = islist(alert_type) ? pick(alert_type) : alert_type + + feedback_details += "Alert type: [alert_category], Actual type: [alert_type]" + + hallucinator.throw_alert( + category = alert_category, + type = picked_type, + severity = optional_severity, + override = TRUE, + ) + + del_timer_id = QDEL_IN(src, duration) + return TRUE + +/datum/hallucination/fake_alert/need_oxygen + alert_category = ALERT_NOT_ENOUGH_OXYGEN + alert_type = /atom/movable/screen/alert/not_enough_oxy + +/datum/hallucination/fake_alert/need_plasma + alert_category = ALERT_NOT_ENOUGH_PLASMA + alert_type = /atom/movable/screen/alert/not_enough_plas + +/datum/hallucination/fake_alert/need_co2 + alert_category = ALERT_NOT_ENOUGH_CO2 + alert_type = /atom/movable/screen/alert/not_enough_co2 + +/datum/hallucination/fake_alert/bad_oxygen + alert_category = ALERT_TOO_MUCH_OXYGEN + alert_type = /atom/movable/screen/alert/too_much_oxy + +/datum/hallucination/fake_alert/bad_plasma + alert_category = ALERT_TOO_MUCH_PLASMA + alert_type = /atom/movable/screen/alert/too_much_plas + +/datum/hallucination/fake_alert/bad_co2 + alert_category = ALERT_TOO_MUCH_CO2 + alert_type = /atom/movable/screen/alert/too_much_co2 + +/datum/hallucination/fake_alert/nutrition + alert_category = ALERT_NUTRITION + alert_type = list(/atom/movable/screen/alert/fat, /atom/movable/screen/alert/starving) + +/datum/hallucination/fake_alert/gravity + alert_category = ALERT_GRAVITY + alert_type = /atom/movable/screen/alert/weightless + +/datum/hallucination/fake_alert/fire + alert_category = ALERT_FIRE + alert_type = /atom/movable/screen/alert/fire + +/datum/hallucination/fake_alert/hot + alert_category = ALERT_TEMPERATURE + alert_type = /atom/movable/screen/alert/hot + optional_severity = 3 + +/datum/hallucination/fake_alert/cold + alert_category = ALERT_TEMPERATURE + alert_type = /atom/movable/screen/alert/cold + optional_severity = 3 + +/datum/hallucination/fake_alert/pressure + alert_category = ALERT_PRESSURE + alert_type = list(/atom/movable/screen/alert/highpressure, /atom/movable/screen/alert/lowpressure) + optional_severity = 2 + +/datum/hallucination/fake_alert/law + alert_category = ALERT_NEW_LAW + alert_type = /atom/movable/screen/alert/newlaw + +/datum/hallucination/fake_alert/locked + alert_category = ALERT_LOCKED + alert_type = /atom/movable/screen/alert/locked + +/datum/hallucination/fake_alert/hacked + alert_category = ALERT_HACKED + alert_type = /atom/movable/screen/alert/hacked + +/datum/hallucination/fake_alert/need_charge + alert_category = ALERT_CHARGE + alert_type = /atom/movable/screen/alert/emptycell diff --git a/code/modules/hallucination/fake_chat.dm b/code/modules/hallucination/fake_chat.dm new file mode 100644 index 00000000000..07221e4b776 --- /dev/null +++ b/code/modules/hallucination/fake_chat.dm @@ -0,0 +1,89 @@ +/// Sends a fake chat message to the hallucinator. +/datum/hallucination/chat + random_hallucination_weight = 100 + + /// If TRUE, we force the message to be hallucinated from common radio. Only set in New() + var/force_radio + /// If set, a message we force to be picked, rather than an auto-generated message. Only set in New() + var/specific_message + +/datum/hallucination/chat/New(mob/living/hallucinator, force_radio = FALSE, specific_message) + src.force_radio = force_radio + src.specific_message = specific_message + return ..() + +/datum/hallucination/chat/start() + var/mob/living/carbon/human/speaker + var/datum/language/understood_language = hallucinator.get_random_understood_language() + for(var/mob/living/carbon/nearby_human in view(hallucinator)) + if(nearby_human == hallucinator) + continue + + if(!speaker) + speaker = nearby_human + else if(get_dist(hallucinator, nearby_human) < get_dist(hallucinator, speaker)) + speaker = nearby_human + + // Get person to affect if radio hallucination + var/is_radio = !speaker || force_radio + if(is_radio) + var/list/humans = list() + for(var/mob/living/carbon/human/existing_human in GLOB.alive_mob_list) + humans += existing_human + speaker = pick(humans) + + // Time to generate a message. + // Spans of our message + var/spans = list(speaker.speech_span) + + // Contents of our message + var/chosen = specific_message + // If we didn't have a preset one, let's make one up. + if(!chosen) + if(is_radio) + chosen = pick(list("Help!", + "[pick_list_replacements(HALLUCINATION_FILE, "people")] is [pick_list_replacements(HALLUCINATION_FILE, "accusations")]!", + "[pick_list_replacements(HALLUCINATION_FILE, "threat")] in [pick_list_replacements(HALLUCINATION_FILE, "location")][prob(50)?"!":"!!"]", + "[pick("Where's [hallucinator.first_name()]?", "Set [hallucinator.first_name()] to arrest!")]", + "[pick("C","Ai, c","Someone c","Rec")]all the shuttle!", + "AI [pick("rogue", "is dead")]!!", + "Borgs rogue!", + )) + else + chosen = pick(list("[pick_list_replacements(HALLUCINATION_FILE, "suspicion")]", + "[pick_list_replacements(HALLUCINATION_FILE, "conversation")]", + "[pick_list_replacements(HALLUCINATION_FILE, "greetings")][hallucinator.first_name()]!", + "[pick_list_replacements(HALLUCINATION_FILE, "getout")]", + "[pick_list_replacements(HALLUCINATION_FILE, "weird")]", + "[pick_list_replacements(HALLUCINATION_FILE, "didyouhearthat")]", + "[pick_list_replacements(HALLUCINATION_FILE, "doubt")]", + "[pick_list_replacements(HALLUCINATION_FILE, "aggressive")]", + "[pick_list_replacements(HALLUCINATION_FILE, "help")]!!", + "[pick_list_replacements(HALLUCINATION_FILE, "escape")]", + "I'm infected, [pick_list_replacements(HALLUCINATION_FILE, "infection_advice")]!", + )) + + chosen = capitalize(chosen) + + chosen = replacetext(chosen, "%TARGETNAME%", hallucinator.first_name()) + + // Log the message + feedback_details += "Type: [is_radio ? "Radio" : "Talk"], Source: [speaker.real_name], Message: [chosen]" + + var/plus_runechat = hallucinator.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat) + + // Display the message + if(!is_radio && !plus_runechat) + var/image/speech_overlay = image('icons/mob/effects/talk.dmi', speaker, "default0", layer = ABOVE_MOB_LAYER) + INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, speech_overlay, list(hallucinator.client), 30) + + if(plus_runechat) + hallucinator.create_chat_message(speaker, understood_language, chosen, spans) + + // And actually show them the message, for real. + var/message = hallucinator.compose_message(speaker, understood_language, chosen, is_radio ? "[FREQ_COMMON]" : null, spans, face_name = TRUE) + to_chat(hallucinator, message) + + // Then clean up. + qdel(src) + return TRUE diff --git a/code/modules/hallucination/fake_death.dm b/code/modules/hallucination/fake_death.dm new file mode 100644 index 00000000000..c6c0bc1e3c7 --- /dev/null +++ b/code/modules/hallucination/fake_death.dm @@ -0,0 +1,82 @@ +/datum/hallucination/death + random_hallucination_weight = 1 + +/datum/hallucination/death/Destroy() + if(!QDELETED(hallucinator)) + hallucinator.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_dead, type) + + return ..() + +/datum/hallucination/death/start() + hallucinator.Paralyze(30 SECONDS) + hallucinator.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_dead, type) + + if(iscarbon(hallucinator)) + var/mob/living/carbon/carbon_hallucinator = hallucinator + carbon_hallucinator.silent += 10 + + to_chat(hallucinator, span_deadsay("[hallucinator.real_name] has died at [get_area_name(hallucinator)].")) + + var/delay = 0 + + if(prob(50)) + var/mob/who_is_salting + if(length(GLOB.dead_player_list)) + who_is_salting = pick(GLOB.dead_mob_list) + + if(who_is_salting) + delay = rand(2 SECONDS, 5 SECONDS) + + var/static/list/things_to_hate = list( + "admins", + "batons", + "blood cult", + "coders", + "heretics", + "myself", + "revenants", + "revs", + "sec", + "ss13", + "this game", + "this round", + "this shift", + "this shit", + "this", + "wizards", + "you", + ) + + var/list/dead_chat_salt = list( + "...", + "FUCK", + "git gud", + "god damn it", + "hey [hallucinator.first_name()]", + "i[prob(50) ? " fucking" : ""] hate [pick(things_to_hate)]", + "is the AI rogue?", + "rip", + "shitsec", + "why did i just drop dead?", + "why was i gibbed", + "wizard?", + "you too?", + ) + + addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, hallucinator, span_deadsay("DEAD: [who_is_salting.name] says, \"[pick(dead_chat_salt)]\"")), delay) + + addtimer(CALLBACK(src, .proc/wake_up), delay + rand(7 SECONDS, 9 SECONDS)) + return TRUE + +/datum/hallucination/death/proc/wake_up() + if(!QDELETED(hallucinator)) + hallucinator.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_dead, type) + + if(iscarbon(hallucinator)) + var/mob/living/carbon/carbon_hallucinator = hallucinator + carbon_hallucinator.silent = 0 + + hallucinator.SetParalyzed(0 SECONDS) + + if(!QDELETED(src)) + qdel(src) diff --git a/code/modules/hallucination/fake_message.dm b/code/modules/hallucination/fake_message.dm new file mode 100644 index 00000000000..43041ef667f --- /dev/null +++ b/code/modules/hallucination/fake_message.dm @@ -0,0 +1,74 @@ +/datum/hallucination/message + random_hallucination_weight = 60 + +/datum/hallucination/message/start() + var/list/nearby_humans = list() + var/adjacent_to_us = FALSE + var/mob/living/carbon/human/suspicious_personnel + for(var/mob/living/carbon/human/nearby_human in oview(hallucinator, 7)) + if(get_dist(nearby_human, hallucinator) <= 1) + suspicious_personnel = nearby_human + adjacent_to_us = TRUE + break + nearby_humans += nearby_human + + if(!suspicious_personnel && length(nearby_humans)) + suspicious_personnel = pick(nearby_humans) + + var/list/message_pool = list() + if(suspicious_personnel) + if(adjacent_to_us) + message_pool[span_warning("You feel a tiny prick!")] = 5 + + var/obj/item/storage/equipped_backpack = suspicious_personnel.get_item_by_slot(ITEM_SLOT_BACK) + if(istype(equipped_backpack)) + // in the future, this could / should be de-harcoded and + // just draw from a pool uplink, theft, and antag item typepaths + var/static/list/stash_items = list( + ".357 revovler", + "energy sword", + "cryptographic sequencer", + "power sink", + "C-4 charge", + "energy bow", + "stun baton", + "flash", + "dart pistol", + "circular saw", + "ritual dagger", + "spellbook", + "Codex Cicatrix", + "captain's spare ID", + "hand teleporter", + "hypospray", + "antique laser gun", + "X-01 MultiPhase Energy Gun", + "station's blueprints", + ) + message_pool[span_notice("[suspicious_personnel] puts the [pick(stash_items)] into [equipped_backpack].")] = 5 + + message_pool["[span_bold("[suspicious_personnel]")] [pick("sneezes", "coughs")]."] = 1 + + message_pool[span_notice("You hear something squeezing through the ducts...")] = 1 + + message_pool[span_warning("Your [pick("arm", "leg", "back", "head")] itches.")] = 1 + message_pool[span_warning("You feel [pick("hot", "cold", "dry", "wet", "woozy", "faint")].")] = 1 + message_pool[span_warning("Your stomach rumbles.")] = 1 + message_pool[span_warning("Your head hurts.")] = 1 + message_pool[span_warning("You hear a faint buzz in your head.")] = 1 + + if(prob(10)) + message_pool[span_warning("Behind you.")] = 1 + message_pool[span_warning("You hear a faint laughter.")] = 1 + message_pool[span_warning("You hear skittering on the ceiling.")] = 1 + message_pool[span_warning("You see an inhumanly tall silhouette moving in the distance.")] = 2 + + if(prob(30)) + var/some_help = pick_list_replacements(HALLUCINATION_FILE, "advice") + message_pool[some_help] = 4 + + var/chosen = pick_weight(message_pool) + feedback_details += "Message: [chosen]" + to_chat(hallucinator, chosen) + qdel(src) + return TRUE diff --git a/code/modules/hallucination/fake_plasmaflood.dm b/code/modules/hallucination/fake_plasmaflood.dm new file mode 100644 index 00000000000..d352b4d7c54 --- /dev/null +++ b/code/modules/hallucination/fake_plasmaflood.dm @@ -0,0 +1,103 @@ + +#define FAKE_FLOOD_EXPAND_TIME 20 +#define FAKE_FLOOD_MAX_RADIUS 10 + +/// Plasma starts flooding from the nearby vent +/datum/hallucination/fake_flood + random_hallucination_weight = 7 + + var/list/image/flood_images = list() + var/list/obj/effect/plasma_image_holder/flood_image_holders = list() + var/list/turf/flood_turfs = list() + var/image_icon = 'icons/effects/atmospherics.dmi' + var/image_state = "plasma" + var/radius = 0 + var/next_expand = 0 + +/datum/hallucination/fake_flood/start() + // This hallucination is purely visual, so we don't need to bother for clientless mobs + if(!hallucinator.client) + return FALSE + + var/turf/center + + for(var/obj/machinery/atmospherics/components/unary/vent_pump/nearby_pump in orange(7, hallucinator)) + if(nearby_pump.welded) + continue + + center = get_turf(nearby_pump) + break + + if(!center) + return FALSE + + feedback_details += "Vent Coords: ([center.x], [center.y], [center.z])" + create_new_plasma_image(center) + hallucinator.client?.images |= flood_images + + next_expand = world.time + FAKE_FLOOD_EXPAND_TIME + START_PROCESSING(SSobj, src) + return TRUE + +/datum/hallucination/fake_flood/process() + if(next_expand > world.time) + return + + radius++ + if(radius > FAKE_FLOOD_MAX_RADIUS) + qdel(src) + return + + expand_flood() + + if(get_turf(hallucinator) in flood_turfs) + var/mob/living/carbon/carbon_hallucinator = hallucinator + if(istype(carbon_hallucinator) && !carbon_hallucinator.internal) + hallucinator.cause_hallucination(/datum/hallucination/fake_alert/bad_plasma, "fake plasmaflood hallucination") + + next_expand = world.time + FAKE_FLOOD_EXPAND_TIME + +/datum/hallucination/fake_flood/proc/expand_flood() + for(var/image/flood_image in flood_images) + flood_image.alpha = min(flood_image.alpha + 50, 255) + + for(var/turf/flooded_turf in flood_turfs) + for(var/dir in GLOB.cardinals) + var/turf/nearby_turf = get_step(flooded_turf, dir) + if((nearby_turf in flood_turfs) || !TURFS_CAN_SHARE(nearby_turf, flooded_turf) || isspaceturf(nearby_turf)) + continue + create_new_plasma_image(nearby_turf) + + hallucinator.client?.images |= flood_images + +/datum/hallucination/fake_flood/proc/create_new_plasma_image(turf/to_flood) + flood_turfs += to_flood + + var/obj/effect/plasma_image_holder/image_holder = new(to_flood) + flood_image_holders += image_holder + + var/image/plasma_image = image(image_icon, image_holder, image_state, FLY_LAYER) + plasma_image.alpha = 50 + plasma_image.plane = ABOVE_GAME_PLANE + flood_images += plasma_image + +/datum/hallucination/fake_flood/Destroy() + STOP_PROCESSING(SSobj, src) + + hallucinator.client?.images -= flood_images + + flood_turfs.Cut() // We don't own these + flood_images.Cut() // We also don't own these, kinda + QDEL_LIST(flood_image_holders) // But we DO own these + + return ..() + +/obj/effect/plasma_image_holder + icon_state = "nothing" + anchored = TRUE + layer = FLY_LAYER + plane = ABOVE_GAME_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +#undef FAKE_FLOOD_EXPAND_TIME +#undef FAKE_FLOOD_MAX_RADIUS diff --git a/code/modules/hallucination/fake_sound.dm b/code/modules/hallucination/fake_sound.dm new file mode 100644 index 00000000000..75788cfb81a --- /dev/null +++ b/code/modules/hallucination/fake_sound.dm @@ -0,0 +1,276 @@ +/// Hallucination that plays a fake sound somewhere nearby. +/datum/hallucination/fake_sound + abstract_hallucination_parent = /datum/hallucination/fake_sound + + /// Volume of the fake sound + var/volume = 50 + /// Whether the fake sound has vary or not + var/sound_vary = TRUE + /// A path to a sound, or a list of sounds, that plays when we trigger + var/sound_type + +/datum/hallucination/fake_sound/start() + var/sound_to_play = islist(sound_type) ? pick(sound_type) : sound_type + play_fake_sound(random_far_turf(), sound_to_play) + feedback_details += "Sound: [sound_to_play]" + qdel(src) + return TRUE + +/// Actually plays the fake sound. +/datum/hallucination/fake_sound/proc/play_fake_sound(turf/source, sound_to_play = sound_type) + hallucinator.playsound_local(source, sound_to_play, volume, sound_vary) + +/// Used to queue additional, delayed fake sounds via a callback. +/datum/hallucination/fake_sound/proc/queue_fake_sound(turf/source, sound_to_play, volume_override, vary_override, delay) + if(!delay) + CRASH("[type] queued a fake sound without a timer.") + + // Queue the sound to be played with a timer on the mob, not the datum, because we'll probably get qdel'd + addtimer(CALLBACK(hallucinator, /mob/proc/playsound_local, source, sound_to_play, volume_override || volume, vary_override || sound_vary), delay) + +/datum/hallucination/fake_sound/normal + abstract_hallucination_parent = /datum/hallucination/fake_sound/normal + random_hallucination_weight = 5 + +/datum/hallucination/fake_sound/normal/airlock + volume = 30 + sound_type = 'sound/machines/airlock.ogg' + +/datum/hallucination/fake_sound/normal/airlock_pry + volume = 100 + sound_type = 'sound/machines/airlock_alien_prying.ogg' + +/datum/hallucination/fake_sound/normal/airlock_pry/play_fake_sound(turf/source, sound_to_play) + . = ..() + queue_fake_sound(source, 'sound/machines/airlockforced.ogg', 50, TRUE, delay = 5 SECONDS) + +/datum/hallucination/fake_sound/normal/console + volume = 25 + sound_type = 'sound/machines/terminal_prompt.ogg' + +/datum/hallucination/fake_sound/normal/boom + sound_type = list('sound/effects/explosion1.ogg', 'sound/effects/explosion2.ogg') + +/datum/hallucination/fake_sound/normal/distant_boom + sound_type = 'sound/effects/explosionfar.ogg' + +/datum/hallucination/fake_sound/normal/glass + sound_type = list('sound/effects/glassbr1.ogg', 'sound/effects/glassbr2.ogg', 'sound/effects/glassbr3.ogg') + +/datum/hallucination/fake_sound/normal/alarm + volume = 100 + sound_type = 'sound/machines/alarm.ogg' + +/datum/hallucination/fake_sound/normal/beepsky + volume = 35 + sound_type = 'sound/voice/beepsky/freeze.ogg' + +/datum/hallucination/fake_sound/normal/mech + volume = 40 + sound_type = 'sound/mecha/mechstep.ogg' + /// The turf the mech started walking from. + var/turf/mech_source + /// What dir is the mech walking? + var/mech_dir = NORTH + /// How many steps are left in the walk? + var/steps_left = 0 + +/datum/hallucination/fake_sound/normal/mech/Destroy() + mech_source = null + return ..() + +/datum/hallucination/fake_sound/normal/mech/start() + mech_dir = pick(GLOB.cardinals) + steps_left = rand(4, 9) + mech_source = random_far_turf() + + mech_walk() + return TRUE + +/datum/hallucination/fake_sound/normal/mech/proc/mech_walk() + if(QDELETED(src)) + return + + if(prob(75)) + play_fake_sound(mech_source) + mech_source = get_step(mech_source, mech_dir) + else + play_fake_sound(mech_source) + mech_dir = pick(GLOB.cardinals) + + if(--steps_left <= 0) + qdel(src) + + else + addtimer(CALLBACK(src, .proc/mech_walk), 1 SECONDS) + +/datum/hallucination/fake_sound/normal/wall_deconstruction + sound_type = 'sound/items/welder.ogg' + +/datum/hallucination/fake_sound/normal/wall_deconstruction/play_fake_sound(turf/source, sound_to_play) + . = ..() + queue_fake_sound(source, 'sound/items/welder2.ogg', delay = 10.5 SECONDS) + queue_fake_sound(source, 'sound/items/ratchet.ogg', delay = 12 SECONDS) + +/datum/hallucination/fake_sound/normal/door_hacking + sound_type = 'sound/items/screwdriver.ogg' + volume = 30 + +/datum/hallucination/fake_sound/normal/door_hacking/play_fake_sound(turf/source, sound_to_play) + // Make it sound like someone's pulsing a multitool one or multiple times. + // Screwdriver happens immediately... + . = ..() + + var/hacking_time = rand(4 SECONDS, 8 SECONDS) + // Multitool sound. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 0.8 SECONDS) + if(hacking_time > 4.5 SECONDS) + // Another multitool sound if the hacking time is long. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 3 SECONDS) + if(prob(50)) + // Bonus multitool sound, rapidly after the last. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 3.5 SECONDS) + + if(hacking_time > 5.5 SECONDS) + // A final multitool sound if the hacking time is very long. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 5 SECONDS) + + // Crowbarring it open. + queue_fake_sound(source, 'sound/machines/airlockforced.ogg', delay = hacking_time) + +/datum/hallucination/fake_sound/normal/steam + volume = 75 + sound_type = 'sound/machines/steam_hiss.ogg' + +/datum/hallucination/fake_sound/normal/flash + random_hallucination_weight = 2 // "it's revs" + volume = 90 + sound_type = 'sound/weapons/flash.ogg' + +/datum/hallucination/fake_sound/weird + abstract_hallucination_parent = /datum/hallucination/fake_sound/weird + random_hallucination_weight = 1 + + /// if FALSE, we will pass "null" in as the turf source, meaning the sound will just play without direction / etc. + var/no_source = FALSE + +/datum/hallucination/fake_sound/weird/play_fake_sound(turf/source, sound_to_play) + if(no_source) + return ..(null, sound_to_play) + + return ..() + +/datum/hallucination/fake_sound/weird/antag + random_hallucination_weight = 0 // This one's a bit gamey, so I'll leave it disabled by default. Have fun badmins + volume = 90 + sound_vary = FALSE + no_source = TRUE + sound_type = list( + 'sound/ambience/antag/bloodcult.ogg', + 'sound/ambience/antag/clockcultalr.ogg', + 'sound/ambience/antag/ecult_op.ogg', + 'sound/ambience/antag/ling_aler.ogg', + 'sound/ambience/antag/malf.ogg', + //'sound/ambience/antag/new_clock.ogg', // This one's much louder than the others, otherwise I would + 'sound/ambience/antag/ops.ogg', + 'sound/ambience/antag/tatoralert.ogg', + ) + +/datum/hallucination/fake_sound/weird/chimp_event + volume = 90 + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/ambience/antag/monkey.ogg' + +/datum/hallucination/fake_sound/weird/colossus + sound_type = 'sound/magic/clockwork/invoke_general.ogg' + +/datum/hallucination/fake_sound/weird/creepy + +/datum/hallucination/fake_sound/weird/creepy/New(mob/living/hallucinator) + . = ..() + //These sounds are (mostly) taken from Hidden: Source + sound_type = GLOB.creepy_ambience + +/datum/hallucination/fake_sound/weird/curse_sound + volume = 40 + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/magic/curse.ogg' + +/datum/hallucination/fake_sound/weird/game_over + sound_vary = FALSE + sound_type = 'sound/misc/compiler-failure.ogg' + +/datum/hallucination/fake_sound/weird/hallelujah + sound_vary = FALSE + sound_type = 'sound/effects/pray_chaplain.ogg' + +/datum/hallucination/fake_sound/weird/highlander + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/misc/highlander.ogg' + +/datum/hallucination/fake_sound/weird/hyperspace + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/runtime/hyperspace/hyperspace_begin.ogg' + +/datum/hallucination/fake_sound/weird/laugher + sound_type = list( + 'sound/voice/human/womanlaugh.ogg', + 'sound/voice/human/manlaugh1.ogg', + 'sound/voice/human/manlaugh2.ogg', + ) + +/datum/hallucination/fake_sound/weird/phone + volume = 15 + sound_vary = FALSE + sound_type = 'sound/weapons/ring.ogg' + +/datum/hallucination/fake_sound/weird/phone/play_fake_sound(turf/source, sound_to_play) + for(var/next_ring in 1 to 3) + queue_fake_sound(source, sound_to_play, delay = 2.5 SECONDS * next_ring) + + return ..() + +/datum/hallucination/fake_sound/weird/spell + sound_type = list( + 'sound/magic/disintegrate.ogg', + 'sound/magic/ethereal_enter.ogg', + 'sound/magic/ethereal_exit.ogg', + 'sound/magic/fireball.ogg', + 'sound/magic/forcewall.ogg', + 'sound/magic/teleport_app.ogg', + 'sound/magic/teleport_diss.ogg', + ) + +/datum/hallucination/fake_sound/weird/spell/just_jaunt // A few antags use jaunts, so this sound specifically is fun to isolate + sound_type = 'sound/magic/ethereal_enter.ogg' + +/datum/hallucination/fake_sound/weird/summon_sound // Heretic circle sound, notably + volume = 75 + sound_type = 'sound/magic/castsummon.ogg' + +/datum/hallucination/fake_sound/weird/tesloose + volume = 35 + sound_type = 'sound/magic/lightningbolt.ogg' + +/datum/hallucination/fake_sound/weird/tesloose/play_fake_sound(turf/source, sound_to_play) + . = ..() + for(var/next_shock in 1 to rand(2, 4)) + queue_fake_sound(source, sound_to_play, volume_override = volume + (15 * next_shock), delay = 3 SECONDS * next_shock) + +/datum/hallucination/fake_sound/weird/xeno + random_hallucination_weight = 2 // Some of these are ambience sounds too + volume = 25 + sound_type = list( + 'sound/voice/lowHiss1.ogg', + 'sound/voice/lowHiss2.ogg', + 'sound/voice/lowHiss3.ogg', + 'sound/voice/lowHiss4.ogg', + 'sound/voice/hiss1.ogg', + 'sound/voice/hiss2.ogg', + 'sound/voice/hiss3.ogg', + 'sound/voice/hiss4.ogg', + ) diff --git a/code/modules/hallucination/fire.dm b/code/modules/hallucination/fire.dm deleted file mode 100644 index 4566fd884a6..00000000000 --- a/code/modules/hallucination/fire.dm +++ /dev/null @@ -1,89 +0,0 @@ -#define RAISE_FIRE_COUNT 3 -#define RAISE_FIRE_TIME 3 - -/datum/hallucination/fire - var/active = TRUE - var/stage = 0 - var/image/fire_overlay - - var/next_action = 0 - var/times_to_lower_stamina - var/fire_clearing = FALSE - var/increasing_stages = TRUE - var/time_spent = 0 - -/datum/hallucination/fire/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - target.set_fire_stacks(max(target.fire_stacks, 0.1)) //Placebo flammability - fire_overlay = image('icons/mob/effects/onfire.dmi', target, "human_burning", ABOVE_MOB_LAYER) - if(target.client) - target.client.images += fire_overlay - to_chat(target, span_userdanger("You're set on fire!")) - target.throw_alert(ALERT_FIRE, /atom/movable/screen/alert/fire, override = TRUE) - times_to_lower_stamina = rand(5, 10) - addtimer(CALLBACK(src, .proc/start_expanding), 20) - -/datum/hallucination/fire/Destroy() - . = ..() - STOP_PROCESSING(SSfastprocess, src) - -/datum/hallucination/fire/proc/start_expanding() - if (isnull(target)) - qdel(src) - return - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/fire/process(delta_time) - if (isnull(target)) - qdel(src) - return - - if(target.fire_stacks <= 0) - clear_fire() - - time_spent += delta_time - - if (fire_clearing) - next_action -= delta_time - if (next_action < 0) - stage -= 1 - update_temp() - next_action += 3 - else if (increasing_stages) - var/new_stage = min(round(time_spent / RAISE_FIRE_TIME), RAISE_FIRE_COUNT) - if (stage != new_stage) - stage = new_stage - update_temp() - - if (stage == RAISE_FIRE_COUNT) - increasing_stages = FALSE - else if (times_to_lower_stamina) - next_action -= delta_time - if (next_action < 0) - target.adjustStaminaLoss(15) - next_action += 2 - times_to_lower_stamina -= 1 - else - clear_fire() - -/datum/hallucination/fire/proc/update_temp() - if(stage <= 0) - target.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) - else - target.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) - target.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, stage, override = TRUE) - -/datum/hallucination/fire/proc/clear_fire() - if(!active) - return - active = FALSE - target.clear_alert(ALERT_FIRE, clear_override = TRUE) - if(target.client) - target.client.images -= fire_overlay - QDEL_NULL(fire_overlay) - fire_clearing = TRUE - next_action = 0 - -#undef RAISE_FIRE_COUNT -#undef RAISE_FIRE_TIME diff --git a/code/modules/hallucination/hazard.dm b/code/modules/hallucination/hazard.dm index 5278c9d7e8a..dc8f6df865f 100644 --- a/code/modules/hallucination/hazard.dm +++ b/code/modules/hallucination/hazard.dm @@ -1,128 +1,102 @@ -/* Hazard Hallucinations - * - * Contains: - * Lava - * Chasm - * Anomaly - */ +/// Hallucinations that create a hazard somewhere nearby that actually has a danger associated. +/datum/hallucination/hazard + abstract_hallucination_parent = /datum/hallucination/hazard + random_hallucination_weight = 5 -/datum/hallucination/dangerflash - -/datum/hallucination/dangerflash/New(mob/living/carbon/C, forced = TRUE, danger_type) - set waitfor = FALSE - ..() - //Flashes of danger + /// The type of effect we create + var/hazard_type = /obj/effect/client_image_holder/hallucination/danger +/datum/hallucination/hazard/start() var/list/possible_points = list() - for(var/turf/open/floor/F in view(target,world.view)) - possible_points += F - if(possible_points.len) - var/turf/open/floor/danger_point = pick(possible_points) - if(!danger_type) - danger_type = pick("lava","chasm","anomaly") - switch(danger_type) - if("lava") - new /obj/effect/hallucination/danger/lava(danger_point, target) - if("chasm") - new /obj/effect/hallucination/danger/chasm(danger_point, target) - if("anomaly") - new /obj/effect/hallucination/danger/anomaly(danger_point, target) + for(var/turf/open/floor/floor_in_view in view(hallucinator)) + possible_points += floor_in_view - qdel(src) + if(!length(possible_points)) + return FALSE -/obj/effect/hallucination/danger - var/image/image + new hazard_type(pick(possible_points), hallucinator, src) + QDEL_IN(src, rand(20 SECONDS, 45 SECONDS)) + return TRUE -/obj/effect/hallucination/danger/proc/show_icon() +/datum/hallucination/hazard/lava + hazard_type = /obj/effect/client_image_holder/hallucination/danger/lava + +/datum/hallucination/hazard/chasm + hazard_type = /obj/effect/client_image_holder/hallucination/danger/chasm + +/datum/hallucination/hazard/anomaly + hazard_type = /obj/effect/client_image_holder/hallucination/danger/anomaly + +/// These hallucination effects cause side effects when the hallucinator walks into them. +/obj/effect/client_image_holder/hallucination/danger + image_layer = TURF_LAYER + +/obj/effect/client_image_holder/hallucination/danger/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) + . = ..() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = .proc/atom_touched_holder, + COMSIG_ATOM_EXITED = .proc/atom_touched_holder, + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/obj/effect/client_image_holder/hallucination/danger/proc/atom_touched_holder(datum/source, atom/movable/entered) + SIGNAL_HANDLER + + if(!(entered in who_sees_us)) + return + + on_hallucinator_entered(entered) + +/// Applies effects whenever the hallucinator enters the turf with our hallucination present. +/obj/effect/client_image_holder/hallucination/danger/proc/on_hallucinator_entered(mob/living/afflicted) return -/obj/effect/hallucination/danger/proc/clear_icon() - if(image && target.client) - target.client.images -= image - -/obj/effect/hallucination/danger/Initialize(mapload, _target) - . = ..() - target = _target - show_icon() - QDEL_IN(src, rand(200, 450)) - -/obj/effect/hallucination/danger/Destroy() - clear_icon() - . = ..() - -/obj/effect/hallucination/danger/lava +/obj/effect/client_image_holder/hallucination/danger/lava name = "lava" + image_icon = 'icons/turf/floors/lava.dmi' -/obj/effect/hallucination/danger/lava/Initialize(mapload, _target) - . = ..() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = .proc/on_entered, - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/hallucination/danger/lava/show_icon() +/obj/effect/client_image_holder/hallucination/danger/lava/generate_image() var/turf/danger_turf = get_turf(src) - image = image('icons/turf/floors/lava.dmi', src, "lava-[danger_turf.smoothing_junction || 0]", TURF_LAYER) - if(target.client) - target.client.images += image + image_state = "lava-[danger_turf.smoothing_junction || 0]" + return ..() -/obj/effect/hallucination/danger/lava/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(AM == target) - target.adjustStaminaLoss(20) - new /datum/hallucination/fire(target) +/obj/effect/client_image_holder/hallucination/danger/lava/on_hallucinator_entered(mob/living/afflicted) + afflicted.adjustStaminaLoss(20) + afflicted.cause_hallucination(/datum/hallucination/fire, "fake lava hallucination") -/obj/effect/hallucination/danger/chasm +/obj/effect/client_image_holder/hallucination/danger/chasm name = "chasm" + image_icon = 'icons/turf/floors/chasms.dmi' -/obj/effect/hallucination/danger/chasm/Initialize(mapload, _target) - . = ..() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = .proc/on_entered, - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/hallucination/danger/chasm/show_icon() +/obj/effect/client_image_holder/hallucination/danger/chasm/generate_image() var/turf/danger_turf = get_turf(src) - image = image('icons/turf/floors/chasms.dmi', src, "chasms-[danger_turf.smoothing_junction || 0]", TURF_LAYER) - if(target.client) - target.client.images += image + image_state = "chasms-[danger_turf.smoothing_junction || 0]" + return ..() -/obj/effect/hallucination/danger/chasm/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(AM == target) - if(istype(target, /obj/effect/dummy/phased_mob)) - return - to_chat(target, span_userdanger("You fall into the chasm!")) - target.Paralyze(40) - addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, target, span_notice("It's surprisingly shallow.")), 15) - QDEL_IN(src, 30) +/obj/effect/client_image_holder/hallucination/danger/chasm/on_hallucinator_entered(mob/living/afflicted) + to_chat(afflicted, span_userdanger("You fall into the chasm!")) + afflicted.visible_message(span_warning("[afflicted] falls to the ground suddenly!"), ignored_mobs = afflicted) + afflicted.Paralyze(4 SECONDS) + addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, afflicted, span_notice("...It's surprisingly shallow.")), 1.5 SECONDS) + QDEL_IN(src, 3 SECONDS) -/obj/effect/hallucination/danger/anomaly +/obj/effect/client_image_holder/hallucination/danger/anomaly name = "flux wave anomaly" + image_icon = 'icons/effects/anomalies.dmi' + image_state = "flux" + image_layer = OBJ_LAYER + 0.01 -/obj/effect/hallucination/danger/anomaly/Initialize(mapload) +/obj/effect/client_image_holder/hallucination/danger/anomaly/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) . = ..() START_PROCESSING(SSobj, src) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = .proc/on_entered, - ) - AddElement(/datum/element/connect_loc, loc_connections) -/obj/effect/hallucination/danger/anomaly/process(delta_time) - if(DT_PROB(45, delta_time)) - step(src,pick(GLOB.alldirs)) - -/obj/effect/hallucination/danger/anomaly/Destroy() +/obj/effect/client_image_holder/hallucination/danger/anomaly/Destroy() STOP_PROCESSING(SSobj, src) return ..() -/obj/effect/hallucination/danger/anomaly/show_icon() - image = image('icons/effects/effects.dmi',src,"electricity2",OBJ_LAYER+0.01) - if(target.client) - target.client.images += image +/obj/effect/client_image_holder/hallucination/danger/anomaly/process(delta_time) + if(DT_PROB(ANOMALY_MOVECHANCE, delta_time)) + step(src, pick(GLOB.alldirs)) -/obj/effect/hallucination/danger/anomaly/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(AM == target) - new /datum/hallucination/shock(target) +/obj/effect/client_image_holder/hallucination/danger/anomaly/on_hallucinator_entered(mob/living/afflicted) + afflicted.cause_hallucination(/datum/hallucination/shock, "fake anomaly hallucination") diff --git a/code/modules/hallucination/hostile_mob.dm b/code/modules/hallucination/hostile_mob.dm deleted file mode 100644 index 3f4674d0ffa..00000000000 --- a/code/modules/hallucination/hostile_mob.dm +++ /dev/null @@ -1,171 +0,0 @@ -/* Hostile Mob Hallucinations - * - * Contains: - * Xeno - * Clown - * Bubblegum - */ - -/obj/effect/hallucination/simple/xeno - image_icon = 'icons/mob/nonhuman-player/alien.dmi' - image_state = "alienh_pounce" - -/obj/effect/hallucination/simple/xeno/Initialize(mapload, mob/living/carbon/T) - . = ..() - name = "alien hunter ([rand(1, 1000)])" - -/obj/effect/hallucination/simple/xeno/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - update_icon(ALL, "alienh_pounce") - if(hit_atom == target && target.stat!=DEAD) - target.Paralyze(100) - target.visible_message(span_danger("[target] flails around wildly."),span_userdanger("[name] pounces on you!")) - -// The numbers of seconds it takes to get to each stage of the xeno attack choreography -#define XENO_ATTACK_STAGE_LEAP_AT_TARGET 1 -#define XENO_ATTACK_STAGE_LEAP_AT_PUMP 2 -#define XENO_ATTACK_STAGE_CLIMB 3 -#define XENO_ATTACK_STAGE_FINISH 6 - -/// Xeno crawls from nearby vent,jumps at you, and goes back in -/datum/hallucination/xeno_attack - var/turf/pump_location = null - var/obj/effect/hallucination/simple/xeno/xeno = null - var/time_processing = 0 - var/stage = XENO_ATTACK_STAGE_LEAP_AT_TARGET - -/datum/hallucination/xeno_attack/New(mob/living/carbon/C, forced = TRUE) - ..() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in orange(7,target)) - if(!U.welded) - pump_location = get_turf(U) - break - - if(pump_location) - feedback_details += "Vent Coords: [pump_location.x],[pump_location.y],[pump_location.z]" - xeno = new(pump_location, target) - START_PROCESSING(SSfastprocess, src) - else - qdel(src) - -/datum/hallucination/xeno_attack/process(delta_time) - time_processing += delta_time - - if (time_processing >= stage) - switch (time_processing) - if (XENO_ATTACK_STAGE_FINISH to INFINITY) - to_chat(target, span_notice("[xeno.name] scrambles into the ventilation ducts!")) - qdel(src) - if (XENO_ATTACK_STAGE_CLIMB to XENO_ATTACK_STAGE_FINISH) - to_chat(target, span_notice("[xeno.name] begins climbing into the ventilation system...")) - stage = XENO_ATTACK_STAGE_FINISH - if (XENO_ATTACK_STAGE_LEAP_AT_PUMP to XENO_ATTACK_STAGE_CLIMB) - xeno.update_icon(ALL, "alienh_leap", 'icons/mob/nonhuman-player/alienleap.dmi', -32, -32) - xeno.throw_at(pump_location, 7, 1, spin = FALSE, diagonals_first = TRUE) - stage = XENO_ATTACK_STAGE_CLIMB - if (XENO_ATTACK_STAGE_LEAP_AT_TARGET to XENO_ATTACK_STAGE_LEAP_AT_PUMP) - xeno.update_icon(ALL, "alienh_leap", 'icons/mob/nonhuman-player/alienleap.dmi', -32, -32) - xeno.throw_at(target, 7, 1, spin = FALSE, diagonals_first = TRUE) - stage = XENO_ATTACK_STAGE_LEAP_AT_PUMP - -/datum/hallucination/xeno_attack/Destroy() - . = ..() - - STOP_PROCESSING(SSfastprocess, src) - QDEL_NULL(xeno) - pump_location = null - -#undef XENO_ATTACK_STAGE_LEAP_AT_TARGET -#undef XENO_ATTACK_STAGE_LEAP_AT_PUMP -#undef XENO_ATTACK_STAGE_CLIMB -#undef XENO_ATTACK_STAGE_FINISH - -/obj/effect/hallucination/simple/clown - image_icon = 'icons/mob/simple/animal.dmi' - image_state = "clown" - -/obj/effect/hallucination/simple/clown/Initialize(mapload, mob/living/carbon/T, duration) - ..(loc, T) - name = pick(GLOB.clown_names) - QDEL_IN(src,duration) - -/obj/effect/hallucination/simple/clown/scary - image_state = "scary_clown" - -/obj/effect/hallucination/simple/bubblegum - name = "Bubblegum" - image_icon = 'icons/mob/simple/lavaland/96x96megafauna.dmi' - image_state = "bubblegum" - px = -32 - -/datum/hallucination/oh_yeah - var/obj/effect/hallucination/simple/bubblegum/bubblegum - var/image/fakebroken - var/image/fakerune - var/turf/landing - var/charged - var/next_action = 0 - -/datum/hallucination/oh_yeah/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - . = ..() - var/turf/closed/wall/wall - for(var/turf/closed/wall/W in range(7,target)) - wall = W - break - if(!wall) - return INITIALIZE_HINT_QDEL - feedback_details += "Source: [wall.x],[wall.y],[wall.z]" - - fakebroken = image('icons/turf/floors.dmi', wall, "plating", layer = TURF_LAYER) - landing = get_turf(target) - var/turf/landing_image_turf = get_step(landing, SOUTHWEST) //the icon is 3x3 - fakerune = image('icons/effects/96x96.dmi', landing_image_turf, "landing", layer = ABOVE_OPEN_TURF_LAYER) - fakebroken.override = TRUE - if(target.client) - target.client.images |= fakebroken - target.client.images |= fakerune - target.playsound_local(wall,'sound/effects/meteorimpact.ogg', 150, 1) - bubblegum = new(wall, target) - addtimer(CALLBACK(src, .proc/start_processing), 10) - -/datum/hallucination/oh_yeah/proc/start_processing() - if (isnull(target)) - qdel(src) - return - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/oh_yeah/process(delta_time) - next_action -= delta_time - - if (next_action > 0) - return - - if (get_turf(bubblegum) != landing && target?.stat != DEAD) - if(!landing || (get_turf(bubblegum)).loc.z != landing.loc.z) - qdel(src) - return - bubblegum.forceMove(get_step_towards(bubblegum, landing)) - bubblegum.setDir(get_dir(bubblegum, landing)) - target.playsound_local(get_turf(bubblegum), 'sound/effects/meteorimpact.ogg', 150, 1) - shake_camera(target, 2, 1) - if(bubblegum.Adjacent(target) && !charged) - charged = TRUE - target.Paralyze(80) - target.adjustStaminaLoss(40) - step_away(target, bubblegum) - shake_camera(target, 4, 3) - target.visible_message(span_warning("[target] jumps backwards, falling on the ground!"),span_userdanger("[bubblegum] slams into you!")) - next_action = 0.2 - else - STOP_PROCESSING(SSfastprocess, src) - QDEL_IN(src, 3 SECONDS) - -/datum/hallucination/oh_yeah/Destroy() - if(target.client) - target.client.images.Remove(fakebroken) - target.client.images.Remove(fakerune) - QDEL_NULL(fakebroken) - QDEL_NULL(fakerune) - QDEL_NULL(bubblegum) - STOP_PROCESSING(SSfastprocess, src) - return ..() diff --git a/code/modules/hallucination/hud_screw.dm b/code/modules/hallucination/hud_screw.dm new file mode 100644 index 00000000000..88a9fccc078 --- /dev/null +++ b/code/modules/hallucination/hud_screw.dm @@ -0,0 +1,26 @@ +/// Screwyhud, makes the user's health bar hud wonky +/datum/hallucination/screwy_hud + abstract_hallucination_parent = /datum/hallucination/screwy_hud + random_hallucination_weight = 4 + + /// The type of hud we give to the hallucinator + var/screwy_hud_type = SCREWYHUD_NONE + +/datum/hallucination/screwy_hud/start() + hallucinator.apply_status_effect(screwy_hud_type, type) + QDEL_IN(src, rand(10 SECONDS, 25 SECONDS)) + return TRUE + +/datum/hallucination/screwy_hud/Destroy() + if(!QDELETED(hallucinator)) + hallucinator.remove_status_effect(screwy_hud_type, type) + return ..() + +/datum/hallucination/screwy_hud/crit + screwy_hud_type = /datum/status_effect/grouped/screwy_hud/fake_crit + +/datum/hallucination/screwy_hud/dead + screwy_hud_type = /datum/status_effect/grouped/screwy_hud/fake_dead + +/datum/hallucination/screwy_hud/healthy + screwy_hud_type = /datum/status_effect/grouped/screwy_hud/fake_healthy diff --git a/code/modules/hallucination/husk.dm b/code/modules/hallucination/husk.dm deleted file mode 100644 index abe70b4dcec..00000000000 --- a/code/modules/hallucination/husk.dm +++ /dev/null @@ -1,31 +0,0 @@ -/datum/hallucination/husks - var/image/halbody - -/datum/hallucination/husks/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - var/list/possible_points = list() - for(var/turf/open/floor/F in view(target,world.view)) - possible_points += F - if(possible_points.len) - var/turf/open/floor/husk_point = pick(possible_points) - switch(rand(1,4)) - if(1) - var/image/body = image('icons/mob/species/human/human.dmi',husk_point,"husk",TURF_LAYER) - var/matrix/M = matrix() - M.Turn(90) - body.transform = M - halbody = body - if(2,3) - halbody = image('icons/mob/species/human/human.dmi',husk_point,"husk",TURF_LAYER) - if(4) - halbody = image('icons/mob/nonhuman-player/alien.dmi',husk_point,"alienother",TURF_LAYER) - - if(target.client) - target.client.images += halbody - QDEL_IN(src, rand(30,50)) //Only seen for a brief moment. - -/datum/hallucination/husks/Destroy() - target?.client?.images -= halbody - QDEL_NULL(halbody) - return ..() diff --git a/code/modules/hallucination/inhand_fake_item.dm b/code/modules/hallucination/inhand_fake_item.dm new file mode 100644 index 00000000000..199fb138c72 --- /dev/null +++ b/code/modules/hallucination/inhand_fake_item.dm @@ -0,0 +1,146 @@ +/// Hallucinates a fake item in our hands, pockets, or belt or whatever. +/datum/hallucination/fake_item + abstract_hallucination_parent = /datum/hallucination/fake_item + random_hallucination_weight = 1 + + /// A flag of slots this fake item can appear in. + var/valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_BELT|ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET + /// What item should we use as a template, grabbing its icon and icon state and name? + var/obj/item/template_item_type + +/datum/hallucination/fake_item/start() + var/list/slots_free = list() + if(valid_slots & ITEM_SLOT_HANDS) + for(var/hand in hallucinator.get_empty_held_indexes()) + slots_free[ui_hand_position(hand)] = ITEM_SLOT_HANDS + + // These slots are human only, + they have to have a uniform + var/mob/living/carbon/human/human_hallucinator = hallucinator + if(istype(hallucinator) && human_hallucinator.w_uniform) + if((valid_slots & ITEM_SLOT_BELT) && !human_hallucinator.belt) + slots_free[ui_belt] = ITEM_SLOT_BELT + if((valid_slots & ITEM_SLOT_LPOCKET) && !human_hallucinator.l_store) + slots_free[ui_storage1] = ITEM_SLOT_LPOCKET + if((valid_slots & ITEM_SLOT_RPOCKET) && !human_hallucinator.r_store) + slots_free[ui_storage2] = ITEM_SLOT_RPOCKET + + if(!length(slots_free)) + return FALSE + + var/picked_space = pick(slots_free) + var/obj/item/hallucinated/hallucinated_item = make_fake_item(picked_space, slots_free[picked_space]) + feedback_details += "Item Type: [hallucinated_item.name]" + + hallucinator.client?.screen += hallucinated_item + QDEL_IN(src, rand(15 SECONDS, 35 SECONDS)) + return TRUE + +/datum/hallucination/fake_item/proc/make_fake_item(where_to_put_it, equip_flags) + var/obj/item/hallucinated/hallucinated_item = new(hallucinator, src) + hallucinated_item.screen_loc = where_to_put_it + + hallucinated_item.name = initial(template_item_type.name) + hallucinated_item.desc = initial(template_item_type.desc) + hallucinated_item.icon = initial(template_item_type.icon) + hallucinated_item.icon_state = initial(template_item_type.icon_state) + hallucinated_item.w_class = initial(template_item_type.w_class) // Not strictly necessary, but keen eyed people will notice + + return hallucinated_item + +/datum/hallucination/fake_item/c4 + template_item_type = /obj/item/grenade/c4 + +/datum/hallucination/fake_item/c4/make_fake_item(where_to_put_it, equip_flags) + if(prob(50)) + template_item_type = /obj/item/grenade/c4/x4 + return ..() + +/datum/hallucination/fake_item/revolver + template_item_type = /obj/item/gun/ballistic/revolver + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_BELT + +/datum/hallucination/fake_item/esword + template_item_type = /obj/item/melee/energy/sword/saber + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET + +/datum/hallucination/fake_item/esword/make_fake_item(where_to_put_it, equip_flags) + // Make the item via parent call + var/obj/item/hallucinated/hallucinated_item = ..() + + // If we were placed in our mob's hands there's a 40% chance to make it appear active + if((equip_flags & ITEM_SLOT_HANDS) && prob(40)) + var/obj/item/melee/energy/sword/saber/sabre_color = pick(subtypesof(/obj/item/melee/energy/sword/saber)) + // Yes this can break if someone changes esword icon stuff + hallucinated_item.icon_state = "[hallucinated_item.icon_state]_on_[initial(sabre_color.sword_color_icon)]" + hallucinator.playsound_local(get_turf(hallucinator), 'sound/weapons/saberon.ogg', 35, TRUE) + + return hallucinated_item + +/datum/hallucination/fake_item/baton + template_item_type = /obj/item/melee/baton/security/loaded + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_BELT + +/datum/hallucination/fake_item/baton/make_fake_item(where_to_put_it, equip_flags) + var/obj/item/hallucinated/hallucinated_item = ..() + + // If we were placed in our mob's hands there's a 30% chance to make it appear active + if((equip_flags & ITEM_SLOT_HANDS) && prob(30)) + // Yes this can break if someone changes baton icon stuff + hallucinated_item.icon_state = "[hallucinated_item.icon_state]_active" + hallucinator.playsound_local(get_turf(hallucinator), SFX_SPARKS, 75, TRUE, -1) + + return hallucinated_item + +/datum/hallucination/fake_item/emag + template_item_type = /obj/item/card/emag + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET + +/datum/hallucination/fake_item/emag/make_fake_item(where_to_put_it, equip_flags) + if(prob(50)) + template_item_type = /obj/item/card/emag/doorjack + return ..() + +/datum/hallucination/fake_item/flashbang + template_item_type = /obj/item/grenade/flashbang + valid_slots = ITEM_SLOT_HANDS + +/datum/hallucination/fake_item/flashbang/make_fake_item(where_to_put_it, equip_flags) + var/obj/item/hallucinated/hallucinated_item = ..() + if(prob(15)) + // Yes this can break if someone changse grenade icon stuff + hallucinated_item.icon_state = "[hallucinated_item.icon_state]_active" + hallucinator.playsound_local(get_turf(hallucinator), 'sound/weapons/armbomb.ogg', 60, TRUE) + to_chat(hallucinator, span_warning("You prime [hallucinated_item]! 5 seconds!")) + + return hallucinated_item + +/obj/item/hallucinated + name = "mirage" + plane = ABOVE_HUD_PLANE + interaction_flags_item = NONE + item_flags = ABSTRACT | DROPDEL | EXAMINE_SKIP | HAND_ITEM | NOBLUDGEON // Most of these flags don't matter, but better safe than sorry + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + /// The hallucination that created us. + var/datum/hallucination/parent + +/obj/item/hallucinated/Initialize(mapload, datum/hallucination/parent) + . = ..() + if(!parent) + stack_trace("[type] was created without a parent hallucination.") + return INITIALIZE_HINT_QDEL + + RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/parent_deleting) + src.parent = parent + + ADD_TRAIT(src, TRAIT_NODROP, INNATE_TRAIT) + +/obj/item/hallucinated/Destroy(force) + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + parent = null + return ..() + +/// Signal proc for [COMSIG_PARENT_QDELETING], if our associated hallucination deletes, we should too +/obj/item/hallucinated/proc/parent_deleting(datum/source) + SIGNAL_HANDLER + + qdel(src) diff --git a/code/modules/hallucination/item.dm b/code/modules/hallucination/item.dm deleted file mode 100644 index bc659dd3811..00000000000 --- a/code/modules/hallucination/item.dm +++ /dev/null @@ -1,172 +0,0 @@ -/* Item Hallucinations - * - * Contains: - * Putting items in other nearby peoples hands - * Putting items in your hands - */ - -/datum/hallucination/items_other - -/datum/hallucination/items_other/New(mob/living/carbon/C, forced = TRUE, item_type) - set waitfor = FALSE - ..() - var/item - if(!item_type) - item = pick(list("esword","taser","ebow","baton","dual_esword","ttv","flash","armblade")) - else - item = item_type - feedback_details += "Item: [item]" - var/side - var/image_file - var/image/A = null - var/list/mob_pool = list() - - for(var/mob/living/carbon/human/M in view(7,target)) - if(M != target) - mob_pool += M - if(!mob_pool.len) - return - - var/mob/living/carbon/human/H = pick(mob_pool) - feedback_details += " Mob: [H.real_name]" - - var/free_hand = H.get_empty_held_index_for_side(LEFT_HANDS) - if(free_hand) - side = "left" - else - free_hand = H.get_empty_held_index_for_side(RIGHT_HANDS) - if(free_hand) - side = "right" - - if(side) - switch(item) - if("esword") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - target.playsound_local(H, 'sound/weapons/saberon.ogg',35,1) - A = image(image_file,H,"e_sword_on_red", layer=ABOVE_MOB_LAYER) - if("dual_esword") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - target.playsound_local(H, 'sound/weapons/saberon.ogg',35,1) - A = image(image_file,H,"dualsaberred1", layer=ABOVE_MOB_LAYER) - if("taser") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - A = image(image_file,H,"advtaserstun4", layer=ABOVE_MOB_LAYER) - if("ebow") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - A = image(image_file,H,"crossbow", layer=ABOVE_MOB_LAYER) - if("baton") - if(side == "right") - image_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - else - image_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - target.playsound_local(H, SFX_SPARKS,75,1,-1) - A = image(image_file,H,"baton", layer=ABOVE_MOB_LAYER) - if("ttv") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' - A = image(image_file,H,"ttv", layer=ABOVE_MOB_LAYER) - if("flash") - if(side == "right") - image_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - else - image_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - A = image(image_file,H,"flashtool", layer=ABOVE_MOB_LAYER) - if("armblade") - if(side == "right") - image_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' - else - image_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' - target.playsound_local(H, 'sound/effects/blobattack.ogg',30,1) - A = image(image_file,H,"arm_blade", layer=ABOVE_MOB_LAYER) - if(target.client) - target.client.images |= A - addtimer(CALLBACK(src, .proc/cleanup, item, A, H), rand(15 SECONDS, 25 SECONDS)) - return - qdel(src) - -/datum/hallucination/items_other/proc/cleanup(item, atom/image_used, has_the_item) - if (isnull(target)) - qdel(src) - return - if(item == "esword" || item == "dual_esword") - target.playsound_local(has_the_item, 'sound/weapons/saberoff.ogg',35,1) - if(item == "armblade") - target.playsound_local(has_the_item, 'sound/effects/blobattack.ogg',30,1) - target.client.images.Remove(image_used) - qdel(src) - -/datum/hallucination/items/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - //Strange items - - var/obj/halitem = new - - halitem = new - var/obj/item/l_hand = target.get_item_for_held_index(1) - var/obj/item/r_hand = target.get_item_for_held_index(2) - var/l = ui_hand_position(target.get_held_index_of_item(l_hand)) - var/r = ui_hand_position(target.get_held_index_of_item(r_hand)) - var/list/slots_free = list(l,r) - if(l_hand) - slots_free -= l - if(r_hand) - slots_free -= r - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(!H.belt) - slots_free += ui_belt - if(!H.l_store) - slots_free += ui_storage1 - if(!H.r_store) - slots_free += ui_storage2 - if(slots_free.len) - halitem.screen_loc = pick(slots_free) - halitem.plane = ABOVE_HUD_PLANE - switch(rand(1,6)) - if(1) //revolver - halitem.icon = 'icons/obj/weapons/guns/ballistic.dmi' - halitem.icon_state = "revolver" - halitem.name = "Revolver" - if(2) //c4 - halitem.icon = 'icons/obj/weapons/grenade.dmi' - halitem.icon_state = "plastic-explosive0" - halitem.name = "C4" - if(prob(25)) - halitem.icon_state = "plasticx40" - if(3) //sword - halitem.icon = 'icons/obj/weapons/transforming_energy.dmi' - halitem.icon_state = "e_sword" - halitem.name = "energy sword" - if(4) //stun baton - halitem.icon = 'icons/obj/weapons/items_and_weapons.dmi' - halitem.icon_state = "stunbaton" - halitem.name = "Stun Baton" - if(5) //emag - halitem.icon = 'icons/obj/card.dmi' - halitem.icon_state = "emag" - halitem.name = "Cryptographic Sequencer" - if(6) //flashbang - halitem.icon = 'icons/obj/weapons/grenade.dmi' - halitem.icon_state = "flashbang1" - halitem.name = "Flashbang" - feedback_details += "Type: [halitem.name]" - if(target.client) - target.client.screen += halitem - QDEL_IN(halitem, rand(150, 350)) - - qdel(src) diff --git a/code/modules/hallucination/nearby_fake_item.dm b/code/modules/hallucination/nearby_fake_item.dm new file mode 100644 index 00000000000..c69556cfffa --- /dev/null +++ b/code/modules/hallucination/nearby_fake_item.dm @@ -0,0 +1,129 @@ +/// A hallucination that delivers the illusion that someone nearby has pulled out a weapon or item. +/datum/hallucination/nearby_fake_item + abstract_hallucination_parent = /datum/hallucination/nearby_fake_item + random_hallucination_weight = 1 + + /// The icon file to draw from for left hand icons + var/left_hand_file + /// The icon file to draw from for right hand icons + var/right_hand_file + /// The icon state of the files to make the image from + var/image_icon_state + /// The image we actually generate + var/image/generated_image + +/datum/hallucination/nearby_fake_item/Destroy() + if(generated_image) + hallucinator.client?.images -= generated_image + generated_image = null + return ..() + +/datum/hallucination/nearby_fake_item/start() + // This hallucination is purely visual, so we don't need to bother for clientless mobs + if(!hallucinator.client) + return FALSE + + var/list/mob_pool = list() + for(var/mob/living/carbon/human/nearby_mob in view(7, hallucinator)) + if(nearby_mob == hallucinator) + continue + mob_pool += nearby_mob + + if(!length(mob_pool)) + return FALSE + + var/mob/living/carbon/human/who_has_the_item = pick(mob_pool) + feedback_details += "Mob: [who_has_the_item.real_name]" + + if(who_has_the_item.get_empty_held_index_for_side(LEFT_HANDS)) + generated_image = generate_fake_image(who_has_the_item, file = left_hand_file) + + else if(who_has_the_item.get_empty_held_index_for_side(RIGHT_HANDS)) + generated_image = generate_fake_image(who_has_the_item, file = right_hand_file) + + if(generated_image) + hallucinator.client?.images += generated_image + addtimer(CALLBACK(src, .proc/remove_image, who_has_the_item), rand(15 SECONDS, 25 SECONDS)) + return TRUE + + return FALSE + +/// Generates the image with the given file on the passed mob. +/datum/hallucination/nearby_fake_item/proc/generate_fake_image(mob/living/carbon/human/holder, file) + return image(file, holder, image_icon_state, layer = ABOVE_MOB_LAYER) + +/// Remove the image when all's said and done. +/datum/hallucination/nearby_fake_item/proc/remove_image(mob/living/carbon/human/holder) + if(QDELETED(src) || QDELETED(hallucinator) || !generated_image) + return + + hallucinator.client?.images -= generated_image + generated_image = null + qdel(src) + +/datum/hallucination/nearby_fake_item/e_sword + left_hand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + image_icon_state = "e_sword_on_red" + +/datum/hallucination/nearby_fake_item/e_sword/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), 'sound/weapons/saberon.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/e_sword/remove_image(mob/living/carbon/human/holder) + if(!QDELETED(holder)) + hallucinator.playsound_local(get_turf(holder), 'sound/weapons/saberoff.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/e_sword/double_bladed + image_icon_state = "dualsaberred1" + +/datum/hallucination/nearby_fake_item/taser + left_hand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + image_icon_state = "advtaserstun4" + +/datum/hallucination/nearby_fake_item/taser/ebow // OOP be like. + image_icon_state = "crossbow" + +/datum/hallucination/nearby_fake_item/baton + left_hand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + image_icon_state = "baton" + +/datum/hallucination/nearby_fake_item/baton/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), SFX_SPARKS, 75, TRUE, -1) + return ..() + +/datum/hallucination/nearby_fake_item/flash + left_hand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + image_icon_state = "flashtool" + +/datum/hallucination/nearby_fake_item/flash/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), 'sound/items/handling/component_pickup.ogg', 35, vary = FALSE) + return ..() + +/datum/hallucination/nearby_fake_item/flash/remove_image(mob/living/carbon/human/holder) + if(!QDELETED(holder)) + hallucinator.playsound_local(get_turf(holder), 'sound/items/handling/component_drop.ogg', 35, vary = FALSE) + return ..() + +/datum/hallucination/nearby_fake_item/armblade + left_hand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' + image_icon_state = "arm_blade" + +/datum/hallucination/nearby_fake_item/armblade/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), 'sound/effects/blobattack.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/armblade/remove_image(mob/living/carbon/human/holder) + if(!QDELETED(holder)) + hallucinator.playsound_local(get_turf(holder), 'sound/effects/blobattack.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/ttv + left_hand_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' + image_icon_state = "ttv" diff --git a/code/modules/hallucination/on_fire.dm b/code/modules/hallucination/on_fire.dm new file mode 100644 index 00000000000..bc3696f8c0e --- /dev/null +++ b/code/modules/hallucination/on_fire.dm @@ -0,0 +1,120 @@ +#define RAISE_FIRE_COUNT 3 +#define RAISE_FIRE_TIME 3 + +/datum/hallucination/fire + random_hallucination_weight = 3 + + /// Are we currently burning our mob? + var/active = TRUE + /// What stare of fire are we in? + var/stage = 0 + + /// What icon file to use for our hallucinator + var/fire_icon = 'icons/mob/effects/onfire.dmi' + /// What icon state to use for our hallucinator + var/fire_icon_state = "human_big_fire" + /// Our fire overlay we generate + var/image/fire_overlay + + /// When should we do our next action of the hallucination? + var/next_action = 0 + /// How may times do we apply stamina damage to our mob? + var/times_to_lower_stamina + /// Are we currently fake-clearing our hallucinated fire? + var/fire_clearing = FALSE + /// Are the stages going up or down? + var/increasing_stages = TRUE + /// How long have we spent on fire? + var/time_spent = 0 + +/datum/hallucination/fire/New(mob/living/hallucinator) + if(ismonkey(hallucinator)) + fire_icon_state = "monkey_big_fire" + + else if(!ishuman(hallucinator)) + fire_icon_state = "generic_fire" + + return ..() + +/datum/hallucination/fire/start() + hallucinator.set_fire_stacks(max(hallucinator.fire_stacks, 0.1)) //Placebo flammability + fire_overlay = image(fire_icon, hallucinator, fire_icon_state, ABOVE_MOB_LAYER) + hallucinator.client?.images |= fire_overlay + to_chat(hallucinator, span_userdanger("You're set on fire!")) + hallucinator.throw_alert(ALERT_FIRE, /atom/movable/screen/alert/fire, override = TRUE) + times_to_lower_stamina = rand(5, 10) + addtimer(CALLBACK(src, .proc/start_expanding), 2 SECONDS) + return TRUE + +/datum/hallucination/fire/Destroy() + hallucinator.adjust_fire_stacks(-0.1) + hallucinator.clear_alert(ALERT_FIRE, clear_override = TRUE) + hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) + if(fire_overlay) + hallucinator.client?.images -= fire_overlay + fire_overlay = null + + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/hallucination/fire/proc/start_expanding() + if(QDELETED(src)) + return + + START_PROCESSING(SSfastprocess, src) + +/datum/hallucination/fire/process(delta_time) + if(QDELETED(src)) + return + + if(hallucinator.fire_stacks <= 0) + clear_fire() + + time_spent += delta_time + + if(fire_clearing) + next_action -= delta_time + if(next_action < 0) + stage -= 1 + update_temp() + next_action += 3 + + else if(increasing_stages) + var/new_stage = min(round(time_spent / RAISE_FIRE_TIME), RAISE_FIRE_COUNT) + if(stage != new_stage) + stage = new_stage + update_temp() + + if(stage == RAISE_FIRE_COUNT) + increasing_stages = FALSE + + else if(times_to_lower_stamina) + next_action -= delta_time + if(next_action < 0) + hallucinator.adjustStaminaLoss(15) + next_action += 2 + times_to_lower_stamina -= 1 + + else + clear_fire() + +/datum/hallucination/fire/proc/update_temp() + if(stage <= 0) + hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) + else + hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) + hallucinator.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, stage, override = TRUE) + +/datum/hallucination/fire/proc/clear_fire() + if(!active) + return + + active = FALSE + hallucinator.clear_alert(ALERT_FIRE, clear_override = TRUE) + hallucinator.client?.images -= fire_overlay + fire_overlay = null + fire_clearing = TRUE + next_action = 0 + +#undef RAISE_FIRE_COUNT +#undef RAISE_FIRE_TIME diff --git a/code/modules/hallucination/plasma_flood.dm b/code/modules/hallucination/plasma_flood.dm deleted file mode 100644 index ea76106231c..00000000000 --- a/code/modules/hallucination/plasma_flood.dm +++ /dev/null @@ -1,83 +0,0 @@ -#define FAKE_FLOOD_EXPAND_TIME 20 -#define FAKE_FLOOD_MAX_RADIUS 10 - -/obj/effect/plasma_image_holder - icon_state = "nothing" - anchored = TRUE - layer = FLY_LAYER - plane = ABOVE_GAME_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/datum/hallucination/fake_flood - //Plasma starts flooding from the nearby vent - var/turf/center - var/list/flood_images = list() - var/list/flood_image_holders = list() - var/list/turf/flood_turfs = list() - var/image_icon = 'icons/effects/atmospherics.dmi' - var/image_state = "plasma" - var/radius = 0 - var/next_expand = 0 - -/datum/hallucination/fake_flood/New(mob/living/carbon/C, forced = TRUE) - ..() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in orange(7,target)) - if(!U.welded) - center = get_turf(U) - break - if(!center) - qdel(src) - return - feedback_details += "Vent Coords: [center.x],[center.y],[center.z]" - var/obj/effect/plasma_image_holder/pih = new(center) - var/image/plasma_image = image(image_icon, pih, image_state, FLY_LAYER) - plasma_image.alpha = 50 - plasma_image.plane = ABOVE_GAME_PLANE - flood_images += plasma_image - flood_image_holders += pih - flood_turfs += center - if(target.client) - target.client.images |= flood_images - next_expand = world.time + FAKE_FLOOD_EXPAND_TIME - START_PROCESSING(SSobj, src) - -/datum/hallucination/fake_flood/process() - if(next_expand <= world.time) - radius++ - if(radius > FAKE_FLOOD_MAX_RADIUS) - qdel(src) - return - Expand() - if((get_turf(target) in flood_turfs) && !target.internal) - new /datum/hallucination/fake_alert(target, TRUE, ALERT_TOO_MUCH_PLASMA) - next_expand = world.time + FAKE_FLOOD_EXPAND_TIME - -/datum/hallucination/fake_flood/proc/Expand() - for(var/image/I in flood_images) - I.alpha = min(I.alpha + 50, 255) - for(var/turf/FT in flood_turfs) - for(var/dir in GLOB.cardinals) - var/turf/T = get_step(FT, dir) - if((T in flood_turfs) || !TURFS_CAN_SHARE(T, FT) || isspaceturf(T)) //If we've gottem already, or if they're not alright to spread with. - continue - var/obj/effect/plasma_image_holder/pih = new(T) - var/image/new_plasma = image(image_icon, pih, image_state, FLY_LAYER) - new_plasma.alpha = 50 - new_plasma.plane = ABOVE_GAME_PLANE - flood_images += new_plasma - flood_image_holders += pih - flood_turfs += T - if(target.client) - target.client.images |= flood_images - -/datum/hallucination/fake_flood/Destroy() - STOP_PROCESSING(SSobj, src) - qdel(flood_turfs) - flood_turfs = list() - if(target.client) - target.client.images.Remove(flood_images) - qdel(flood_images) - flood_images = list() - qdel(flood_image_holders) - flood_image_holders = list() - return ..() diff --git a/code/modules/hallucination/polymorph.dm b/code/modules/hallucination/polymorph.dm deleted file mode 100644 index 4cd6ae1e85d..00000000000 --- a/code/modules/hallucination/polymorph.dm +++ /dev/null @@ -1,101 +0,0 @@ -/* Polymorph Hallucinations - * - * Contains: - * Nearby mobs are polymorphed into other creatures - * Polymorphing yourself into other creatures - */ -/datum/hallucination/delusion - var/list/image/delusions = list() - -/datum/hallucination/delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300,skip_nearby = TRUE, custom_icon = null, custom_icon_file = null, custom_name = null) - set waitfor = FALSE - . = ..() - var/image/A = null - var/kind = force_kind ? force_kind : pick("nothing","monkey","corgi","carp","skeleton","demon","zombie") - feedback_details += "Type: [kind]" - var/list/nearby - if(skip_nearby) - nearby = get_hearers_in_view(7, target) - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(H == target) - continue - if(skip_nearby && (H in nearby)) - continue - switch(kind) - if("nothing") - A = image('icons/effects/effects.dmi',H,"nothing") - A.name = "..." - if("monkey")//Monkey - A = image('icons/mob/species/human/human.dmi',H,"monkey") - A.name = "Monkey ([rand(1,999)])" - if("carp")//Carp - A = image('icons/mob/simple/carp.dmi',H,"carp") - A.name = "Space Carp" - if("corgi")//Corgi - A = image('icons/mob/simple/pets.dmi',H,"corgi") - A.name = "Corgi" - if("skeleton")//Skeletons - A = image('icons/mob/species/human/human.dmi',H,"skeleton") - A.name = "Skeleton" - if("zombie")//Zombies - A = image('icons/mob/species/human/human.dmi',H,"zombie") - A.name = "Zombie" - if("demon")//Demon - A = image('icons/mob/simple/mob.dmi',H,"daemon") - A.name = "Demon" - if("custom") - A = image(custom_icon_file, H, custom_icon) - A.name = custom_name - A.override = 1 - if(target.client) - delusions |= A - target.client.images |= A - if(duration) - QDEL_IN(src, duration) - -/datum/hallucination/delusion/Destroy() - for(var/image/I in delusions) - if(target.client) - target.client.images.Remove(I) - return ..() - -/datum/hallucination/self_delusion - var/image/delusion - -/datum/hallucination/self_delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300, custom_icon = null, custom_icon_file = null, wabbajack = TRUE) //set wabbajack to false if you want to use another fake source - set waitfor = FALSE - ..() - var/image/A = null - var/kind = force_kind ? force_kind : pick("monkey","corgi","carp","skeleton","demon","zombie","robot") - feedback_details += "Type: [kind]" - switch(kind) - if("monkey")//Monkey - A = image('icons/mob/species/human/human.dmi',target,"monkey") - if("carp")//Carp - A = image('icons/mob/simple/animal.dmi',target,"carp") - if("corgi")//Corgi - A = image('icons/mob/simple/pets.dmi',target,"corgi") - if("skeleton")//Skeletons - A = image('icons/mob/species/human/human.dmi',target,"skeleton") - if("zombie")//Zombies - A = image('icons/mob/species/human/human.dmi',target,"zombie") - if("demon")//Demon - A = image('icons/mob/simple/mob.dmi',target,"daemon") - if("robot")//Cyborg - A = image('icons/mob/silicon/robots.dmi',target,"robot") - target.playsound_local(target,'sound/voice/liveagain.ogg', 75, 1) - if("custom") - A = image(custom_icon_file, target, custom_icon) - A.override = 1 - if(target.client) - if(wabbajack) - to_chat(target, span_hear("...wabbajack...wabbajack...")) - target.playsound_local(target,'sound/magic/staff_change.ogg', 50, 1) - delusion = A - target.client.images |= A - QDEL_IN(src, duration) - -/datum/hallucination/self_delusion/Destroy() - if(target.client) - target.client.images.Remove(delusion) - return ..() diff --git a/code/modules/hallucination/screwy_health_doll.dm b/code/modules/hallucination/screwy_health_doll.dm new file mode 100644 index 00000000000..94863d3590e --- /dev/null +++ b/code/modules/hallucination/screwy_health_doll.dm @@ -0,0 +1,80 @@ +///Causes the target to see incorrect health damages on the healthdoll +/datum/hallucination/fake_health_doll + random_hallucination_weight = 12 + + /// The duration of the hallucination + var/duration + /// Assoc list of [ref to bodyparts] to [severity] + var/list/bodyparts = list() + /// Timer ID for when we're deleted + var/del_timer_id + +/datum/hallucination/fake_health_doll/New(mob/living/hallucinator, duration = 50 SECONDS) + src.duration = duration + return ..() + +// So that the associated addition proc cleans it up correctly +/datum/hallucination/fake_health_doll/Destroy() + if(del_timer_id) + deltimer(del_timer_id) + + for(var/obj/item/bodypart/limb as anything in bodyparts) + remove_bodypart(limb) + + hallucinator.update_health_hud() + return ..() + +/datum/hallucination/fake_health_doll/start() + if(!ishuman(hallucinator)) + return FALSE + + add_fake_limb() + del_timer_id = QDEL_IN(src, duration) + return TRUE + +/// Increments the severity of the damage seen on all the limbs we are already tracking. +/datum/hallucination/fake_health_doll/proc/increment_fake_damage() + + for(var/obj/item/bodypart/limb as anything in bodyparts) + bodyparts[limb] = clamp(bodyparts[limb] + 1, 1, 5) + + hallucinator.update_health_hud() + +/** + * Adds a fake limb to the effect. + * + * specific_limb - optional, the specific limb to apply the effect to. If not passed, picks a random limb + * seveirty - optional, the specific severity level to apply the effect. Clamped from 1 to 5. If not passed, picks a random number. + */ +/datum/hallucination/fake_health_doll/proc/add_fake_limb(obj/item/bodypart/specific_limb, severity) + var/mob/living/carbon/human/human_mob = hallucinator + + var/obj/item/bodypart/picked = specific_limb || pick(human_mob.bodyparts) + if(!(picked in bodyparts)) + RegisterSignal(picked, list(COMSIG_PARENT_QDELETING, COMSIG_BODYPART_REMOVED), .proc/remove_bodypart) + RegisterSignal(picked, COMSIG_BODYPART_UPDATING_HEALTH_HUD, .proc/on_bodypart_hud_update) + RegisterSignal(picked, COMSIG_BODYPART_CHECKED_FOR_INJURY, .proc/on_bodypart_checked) + + bodyparts[picked] = clamp(severity || rand(1, 5), 1, 5) + hallucinator.update_health_hud() + +/// Remove a bodypart from our list, unregistering all associated signals and handling the reference +/datum/hallucination/fake_health_doll/proc/remove_bodypart(obj/item/bodypart/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_PARENT_QDELETING, COMSIG_BODYPART_REMOVED, COMSIG_BODYPART_UPDATING_HEALTH_HUD, COMSIG_BODYPART_CHECKED_FOR_INJURY)) + bodyparts -= source + +/// Whenever a bodypart we're tracking has their health hud updated, override it with our fake overlay +/datum/hallucination/fake_health_doll/proc/on_bodypart_hud_update(obj/item/bodypart/source, mob/living/carbon/human/owner) + SIGNAL_HANDLER + + var/mutable_appearance/fake_overlay = mutable_appearance('icons/hud/screen_gen.dmi', "[source.body_zone][bodyparts[source]]") + owner.hud_used.healthdoll.add_overlay(fake_overlay) + return COMPONENT_OVERRIDE_BODYPART_HEALTH_HUD + +/// Signal proc for [COMSIG_BODYPART_CHECKED_FOR_INJURY]. Our bodyparts look a lot more wounded than they actually are. +/datum/hallucination/fake_health_doll/proc/on_bodypart_checked(obj/item/bodypart/source, mob/living/carbon/examiner, list/check_list, list/limb_damage) + SIGNAL_HANDLER + + limb_damage[BRUTE] = bodyparts[source] * 0.2 * source.max_damage diff --git a/code/modules/hallucination/shock.dm b/code/modules/hallucination/shock.dm index e38057036a8..8176a6c2159 100644 --- a/code/modules/hallucination/shock.dm +++ b/code/modules/hallucination/shock.dm @@ -1,31 +1,63 @@ +/// Causes a fake "zap" to the hallucinator. /datum/hallucination/shock + random_hallucination_weight = 1 + + var/electrocution_icon = 'icons/mob/species/human/human.dmi' + var/electrocution_icon_state = "electrocuted_base" var/image/shock_image var/image/electrocution_skeleton_anim -/datum/hallucination/shock/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - shock_image = image(target, target, dir = target.dir) +/datum/hallucination/shock/New(mob/living/hallucinator) + electrocution_icon_state = ishuman(hallucinator) ? "electrocuted_base" : "electrocuted_generic" + return ..() + +/datum/hallucination/shock/Destroy() + if(shock_image) + hallucinator.client?.images -= shock_image + shock_image = null + if(electrocution_skeleton_anim) + hallucinator.client?.images -= electrocution_skeleton_anim + electrocution_skeleton_anim = null + + return ..() + +/datum/hallucination/shock/start() + shock_image = image(hallucinator, hallucinator, dir = hallucinator.dir) shock_image.appearance_flags |= KEEP_APART - shock_image.color = rgb(0,0,0) + shock_image.color = rgb(0, 0, 0) shock_image.override = TRUE - electrocution_skeleton_anim = image('icons/mob/species/human/human.dmi', target, icon_state = "electrocuted_base", layer=ABOVE_MOB_LAYER) + + electrocution_skeleton_anim = image(electrocution_icon, hallucinator, icon_state = electrocution_icon_state, layer = ABOVE_MOB_LAYER) electrocution_skeleton_anim.appearance_flags |= RESET_COLOR|KEEP_APART - to_chat(target, span_userdanger("You feel a powerful shock course through your body!")) - if(target.client) - target.client.images |= shock_image - target.client.images |= electrocution_skeleton_anim - addtimer(CALLBACK(src, .proc/reset_shock_animation), 40) - target.playsound_local(get_turf(src), SFX_SPARKS, 100, 1) - target.staminaloss += 50 - target.Stun(4 SECONDS) - target.do_jitter_animation(300) // Maximum jitter - target.adjust_timed_status_effect(20 SECONDS, /datum/status_effect/jitter) + + to_chat(hallucinator, span_userdanger("You feel a powerful shock course through your body!")) + hallucinator.visible_message(span_warning("[hallucinator] falls to the ground, shaking!"), ignored_mobs = hallucinator) + hallucinator.client?.images |= shock_image + hallucinator.client?.images |= electrocution_skeleton_anim + + hallucinator.playsound_local(get_turf(src), SFX_SPARKS, 100, TRUE) + hallucinator.adjustStaminaLoss(50) + hallucinator.Stun(4 SECONDS) + hallucinator.do_jitter_animation(300) // Maximum jitter + hallucinator.adjust_timed_status_effect(20 SECONDS, /datum/status_effect/jitter) + + addtimer(CALLBACK(src, .proc/reset_shock_animation), 4 SECONDS) addtimer(CALLBACK(src, .proc/shock_drop), 2 SECONDS) + QDEL_IN(src, 4 SECONDS) + return TRUE /datum/hallucination/shock/proc/reset_shock_animation() - target.client?.images.Remove(shock_image) - target.client?.images.Remove(electrocution_skeleton_anim) + if(QDELETED(hallucinator)) + return + + hallucinator.client?.images -= shock_image + shock_image = null + + hallucinator.client?.images -= electrocution_skeleton_anim + electrocution_skeleton_anim = null /datum/hallucination/shock/proc/shock_drop() - target.Paralyze(6 SECONDS) + if(QDELETED(hallucinator)) + return + + hallucinator.Paralyze(6 SECONDS) diff --git a/code/modules/hallucination/sound.dm b/code/modules/hallucination/sound.dm deleted file mode 100644 index 00dc752a191..00000000000 --- a/code/modules/hallucination/sound.dm +++ /dev/null @@ -1,243 +0,0 @@ -/* Sound Hallucinations - * - * Contains: - * Fighting sounds - * Machinery sounds - * Special effects sounds - */ - -/datum/hallucination/battle - var/battle_type - var/iterations_left - var/hits = 0 - var/next_action = 0 - var/turf/source - -/datum/hallucination/battle/New(mob/living/carbon/C, forced = TRUE, new_battle_type) - ..() - - source = random_far_turf() - - battle_type = new_battle_type - if (isnull(battle_type)) - battle_type = pick("laser", "disabler", "esword", "gun", "stunprod", "harmbaton", "bomb") - feedback_details += "Type: [battle_type]" - var/process = TRUE - - switch(battle_type) - if("disabler", "laser") - iterations_left = rand(5, 10) - if("esword") - iterations_left = rand(4, 8) - target.playsound_local(source, 'sound/weapons/saberon.ogg',15, 1) - if("gun") - iterations_left = rand(3, 6) - if("stunprod") //Stunprod + cablecuff - process = FALSE - target.playsound_local(source, 'sound/weapons/egloves.ogg', 40, 1) - target.playsound_local(source, get_sfx(SFX_BODYFALL), 25, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/weapons/cablecuff.ogg', 15, 1), 20) - if("harmbaton") //zap n slap - iterations_left = rand(5, 12) - target.playsound_local(source, 'sound/weapons/egloves.ogg', 40, 1) - target.playsound_local(source, get_sfx(SFX_BODYFALL), 25, 1) - next_action = 2 SECONDS - if("bomb") // Tick Tock - iterations_left = rand(3, 11) - - if (process) - START_PROCESSING(SSfastprocess, src) - else - qdel(src) - -/datum/hallucination/battle/process(delta_time) - next_action -= (delta_time * 10) - - if (next_action > 0) - return - - switch (battle_type) - if ("disabler", "laser", "gun") - var/fire_sound - var/hit_person_sound - var/hit_wall_sound - var/number_of_hits - var/chance_to_fall - - switch (battle_type) - if ("disabler") - fire_sound = 'sound/weapons/taser2.ogg' - hit_person_sound = 'sound/weapons/tap.ogg' - hit_wall_sound = 'sound/weapons/effects/searwall.ogg' - number_of_hits = 3 - chance_to_fall = 70 - if ("laser") - fire_sound = 'sound/weapons/laser.ogg' - hit_person_sound = 'sound/weapons/sear.ogg' - hit_wall_sound = 'sound/weapons/effects/searwall.ogg' - number_of_hits = 4 - chance_to_fall = 70 - if ("gun") - fire_sound = 'sound/weapons/gun/shotgun/shot.ogg' - hit_person_sound = 'sound/weapons/pierce.ogg' - hit_wall_sound = SFX_RICOCHET - number_of_hits = 2 - chance_to_fall = 80 - - target.playsound_local(source, fire_sound, 25, 1) - - if(prob(50)) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, hit_person_sound, 25, 1), rand(5,10)) - hits += 1 - else - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, hit_wall_sound, 25, 1), rand(5,10)) - - next_action = rand(CLICK_CD_RANGE, CLICK_CD_RANGE + 6) - - if(hits >= number_of_hits && prob(chance_to_fall)) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, get_sfx(SFX_BODYFALL), 25, 1), next_action) - qdel(src) - return - if ("esword") - target.playsound_local(source, 'sound/weapons/blade1.ogg', 50, 1) - - if (hits == 4) - target.playsound_local(source, get_sfx(SFX_BODYFALL), 25, 1) - - next_action = rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 6) - hits += 1 - - if (iterations_left == 1) - target.playsound_local(source, 'sound/weapons/saberoff.ogg', 15, 1) - if ("harmbaton") - target.playsound_local(source, SFX_SWING_HIT, 50, 1) - next_action = rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 4) - if ("bomb") - target.playsound_local(source, 'sound/items/timer.ogg', 25, 0) - next_action = 15 - - iterations_left -= 1 - if (iterations_left == 0) - qdel(src) - -/datum/hallucination/battle/Destroy() - . = ..() - source = null - STOP_PROCESSING(SSfastprocess, src) - -/datum/hallucination/sounds - -/datum/hallucination/sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) - set waitfor = FALSE - ..() - var/turf/source = random_far_turf() - if(!sound_type) - sound_type = pick("airlock","airlock pry","console","explosion","far explosion","mech","glass","alarm","beepsky","mech","wall decon","door hack") - feedback_details += "Type: [sound_type]" - //Strange audio - switch(sound_type) - if("airlock") - target.playsound_local(source,'sound/machines/airlock.ogg', 30, 1) - if("airlock pry") - target.playsound_local(source,'sound/machines/airlock_alien_prying.ogg', 100, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/machines/airlockforced.ogg', 30, 1), 50) - if("console") - target.playsound_local(source,'sound/machines/terminal_prompt.ogg', 25, 1) - if("explosion") - if(prob(50)) - target.playsound_local(source,'sound/effects/explosion1.ogg', 50, 1) - else - target.playsound_local(source, 'sound/effects/explosion2.ogg', 50, 1) - if("far explosion") - target.playsound_local(source, 'sound/effects/explosionfar.ogg', 50, 1) - if("glass") - target.playsound_local(source, pick('sound/effects/glassbr1.ogg','sound/effects/glassbr2.ogg','sound/effects/glassbr3.ogg'), 50, 1) - if("alarm") - target.playsound_local(source, 'sound/machines/alarm.ogg', 100, 0) - if("beepsky") - target.playsound_local(source, 'sound/voice/beepsky/freeze.ogg', 35, 0) - if("mech") - new /datum/hallucination/mech_sounds(C, forced, sound_type) - //Deconstructing a wall - if("wall decon") - target.playsound_local(source, 'sound/items/welder.ogg', 50, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/items/welder2.ogg', 50, 1), 105) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/items/ratchet.ogg', 50, 1), 120) - //Hacking a door - if("door hack") - target.playsound_local(source, 'sound/items/screwdriver.ogg', 50, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/machines/airlockforced.ogg', 30, 1), rand(40, 80)) - qdel(src) - -/datum/hallucination/mech_sounds - var/mech_dir - var/steps_left - var/next_action = 0 - var/turf/source - -/datum/hallucination/mech_sounds/New() - . = ..() - mech_dir = pick(GLOB.cardinals) - steps_left = rand(4, 9) - source = random_far_turf() - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/mech_sounds/process(delta_time) - next_action -= delta_time - if (next_action > 0) - return - - if(prob(75)) - target.playsound_local(source, 'sound/mecha/mechstep.ogg', 40, 1) - source = get_step(source, mech_dir) - else - target.playsound_local(source, 'sound/mecha/mechturn.ogg', 40, 1) - mech_dir = pick(GLOB.cardinals) - - steps_left -= 1 - if (!steps_left) - qdel(src) - return - next_action = 1 - -/datum/hallucination/mech_sounds/Destroy() - . = ..() - STOP_PROCESSING(SSfastprocess, src) - -/datum/hallucination/weird_sounds - -/datum/hallucination/weird_sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) - set waitfor = FALSE - ..() - var/turf/source = random_far_turf() - if(!sound_type) - sound_type = pick("phone","hallelujah","highlander","laughter","hyperspace","game over","creepy","tesla") - feedback_details += "Type: [sound_type]" - //Strange audio - switch(sound_type) - if("phone") - target.playsound_local(source, 'sound/weapons/ring.ogg', 15) - for (var/next_rings in 1 to 3) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/weapons/ring.ogg', 15), 25 * next_rings) - if("hyperspace") - target.playsound_local(null, 'sound/runtime/hyperspace/hyperspace_begin.ogg', 50) - if("hallelujah") - target.playsound_local(source, 'sound/effects/pray_chaplain.ogg', 50) - if("highlander") - target.playsound_local(null, 'sound/misc/highlander.ogg', 50) - if("game over") - target.playsound_local(source, 'sound/misc/compiler-failure.ogg', 50) - if("laughter") - if(prob(50)) - target.playsound_local(source, 'sound/voice/human/womanlaugh.ogg', 50, 1) - else - target.playsound_local(source, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1) - if("creepy") - //These sounds are (mostly) taken from Hidden: Source - target.playsound_local(source, pick(GLOB.creepy_ambience), 50, 1) - if("tesla") //Tesla loose! - target.playsound_local(source, 'sound/magic/lightningbolt.ogg', 35, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/magic/lightningbolt.ogg', 65, 1), 30) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/magic/lightningbolt.ogg', 100, 1), 60) - - qdel(src) diff --git a/code/modules/hallucination/station_message.dm b/code/modules/hallucination/station_message.dm new file mode 100644 index 00000000000..8ce29944506 --- /dev/null +++ b/code/modules/hallucination/station_message.dm @@ -0,0 +1,118 @@ +#define ALERT_TITLE(text) ("

" + text + "

") +#define ALERT_BODY(text) ("

" + span_alert(text) + "

") + +/datum/hallucination/station_message + abstract_hallucination_parent = /datum/hallucination/station_message + random_hallucination_weight = 1 + +/datum/hallucination/station_message/start() + qdel(src) // To be implemented by subtypes, call parent for easy cleanup + return TRUE + +/datum/hallucination/station_message/blob_alert + +/datum/hallucination/station_message/blob_alert/start() + to_chat(hallucinator, ALERT_TITLE("Biohazard Alert")) + to_chat(hallucinator, ALERT_BODY("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK5])) + return ..() + +/datum/hallucination/station_message/shuttle_dock + +/datum/hallucination/station_message/shuttle_dock/start() + to_chat(hallucinator, ALERT_TITLE("Priority Announcement")) + to_chat(hallucinator, ALERT_BODY("[SSshuttle.emergency || "The Emergency Shuttle"] has docked with the station. You have 3 minutes to board the Emergency Shuttle.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLEDOCK])) + return ..() + +/datum/hallucination/station_message/malf_ai + +/datum/hallucination/station_message/malf_ai/start() + if(!(locate(/mob/living/silicon/ai) in GLOB.silicon_mobs)) + return FALSE + + to_chat(hallucinator, ALERT_TITLE("Anomaly Alert")) + to_chat(hallucinator, ALERT_BODY("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_AIMALF])) + return ..() + +/datum/hallucination/station_message/heretic + /// This is gross and will probably easily be outdated in some time but c'est la vie. + /// Maybe if someone datumizes heretic paths or something this can be improved + var/static/list/ascension_bodies = list( + "Fear the blaze, for the Ashlord, %FAKENAME% has ascended! The flames shall consume all!", + "Master of blades, the Torn Champion's disciple, %FAKENAME% has ascended! Their steel is that which will cut reality in a maelstom of silver!", + "Ever coiling vortex. Reality unfolded. ARMS OUTREACHED, THE LORD OF THE NIGHT, %FAKENAME% has ascended! Fear the ever twisting hand!", + "Fear the decay, for the Rustbringer, %FAKENAME% has ascended! None shall escape the corrosion!", + "The nobleman of void %FAKENAME% has arrived, stepping along the Waltz that ends worlds!", + ) + +/datum/hallucination/station_message/heretic/start() + // Unfortunately, this will not be synced if mass hallucinated + var/mob/living/carbon/human/totally_real_heretic = random_non_sec_crewmember() + if(!totally_real_heretic) + return FALSE + + var/message_with_name = pick(ascension_bodies) + message_with_name = replacetext(message_with_name, "%FAKENAME%", totally_real_heretic.real_name) + + to_chat(hallucinator, ALERT_TITLE(generate_heretic_text())) + to_chat(hallucinator, ALERT_BODY("[generate_heretic_text()] [message_with_name] [generate_heretic_text()]")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SPANOMALIES])) + return ..() + +/datum/hallucination/station_message/cult_summon + +/datum/hallucination/station_message/cult_summon/start() + // Same, will not be synced if mass hallucinated + var/mob/living/carbon/human/totally_real_cult_leader = random_non_sec_crewmember() + if(!totally_real_cult_leader) + return FALSE + + // Get a fake area that the summoning is happening in + var/area/hallucinator_area = get_area(hallucinator) + var/area/fake_summon_area_type = pick(GLOB.the_station_areas - hallucinator_area.type) + var/area/fake_summon_area = GLOB.areas_by_type[fake_summon_area_type] + + to_chat(hallucinator, ALERT_TITLE("Central Command Higher Dimensional Affairs")) + to_chat(hallucinator, ALERT_BODY("Figments from an eldritch god are being summoned by [totally_real_cult_leader.real_name] \ + into [fake_summon_area] from an unknown dimension. Disrupt the ritual at all costs!")) + + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SPANOMALIES])) + return ..() + +/datum/hallucination/station_message/meteors + random_hallucination_weight = 2 + +/datum/hallucination/station_message/meteors/start() + to_chat(hallucinator, ALERT_TITLE("Meteor Alert")) + to_chat(hallucinator, ALERT_BODY("Meteors have been detected on collision course with the station.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_METEORS])) + return ..() + +/datum/hallucination/station_message/supermatter_delam + +/datum/hallucination/station_message/supermatter_delam/start() + SEND_SOUND(hallucinator, 'sound/magic/charge.ogg') + to_chat(hallucinator, span_boldannounce("You feel reality distort for a moment...")) + return ..() + +/datum/hallucination/station_message/clock_cult_ark + // Clock cult's long gone, but this stays for posterity. + random_hallucination_weight = 0 + +/datum/hallucination/station_message/clock_cult_ark/start() + hallucinator.playsound_local(hallucinator, 'sound/machines/clockcult/ark_deathrattle.ogg', 50, FALSE, pressure_affected = FALSE) + hallucinator.playsound_local(hallucinator, 'sound/effects/clockcult_gateway_disrupted.ogg', 50, FALSE, pressure_affected = FALSE) + addtimer(CALLBACK(src, .proc/play_distant_explosion_sound), 2.7 SECONDS) + return TRUE // does not call parent to finish up the sound in a few seconds + +/datum/hallucination/station_message/clock_cult_ark/proc/play_distant_explosion_sound() + if(QDELETED(src)) + return + + hallucinator.playsound_local(get_turf(hallucinator), 'sound/effects/explosion_distant.ogg', 50, FALSE, pressure_affected = FALSE) + qdel(src) + +#undef ALERT_TITLE +#undef ALERT_BODY diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm index b0fba6d724a..c45e9301b89 100644 --- a/code/modules/hallucination/stray_bullet.dm +++ b/code/modules/hallucination/stray_bullet.dm @@ -1,21 +1,317 @@ -//hallucination projectile code in code/modules/projectiles/projectile/special.dm +/// Shoots a random, fake projectile to the hallucinator /datum/hallucination/stray_bullet + random_hallucination_weight = 7 + +/datum/hallucination/stray_bullet/start() + var/list/turf/starting_locations = list() + for(var/turf/open/open_out_of_view in view(world.view + 1, hallucinator) - view(world.view, hallucinator)) + starting_locations += open_out_of_view + if(!length(starting_locations)) + return FALSE + + var/turf/start = pick(starting_locations) + var/fake_type = pick(subtypesof(/obj/projectile/hallucination)) + + feedback_details += "Type: [fake_type], Source: ([start.x], [start.y], [start.z])" + + var/obj/projectile/hallucination/fake_projectile = new fake_type(start, src) + + fake_projectile.preparePixelProjectile(hallucinator, start) + fake_projectile.fire() + + QDEL_IN(src, 10 SECONDS) // Should clean up the projectile if it somehow gets stuck. + return TRUE + +/// Hallucinated projectiles. +/obj/projectile/hallucination + name = "bullet" + icon = null + icon_state = null + hitsound = "" + suppressed = SUPPRESSED_VERY + ricochets_max = 0 + ricochet_chance = 0 + damage = 0 + nodamage = TRUE + projectile_type = /obj/projectile/hallucination + log_override = TRUE + /// Our parent hallucination that's created us + var/datum/hallucination/parent + /// The image that represents our projectile itself + var/image/fake_bullet + + var/hal_icon = 'icons/obj/weapons/guns/projectiles.dmi' + var/hal_icon_state + var/hal_fire_sound + var/hal_hitsound + var/hal_hitsound_wall + var/hal_impact_effect + var/hal_impact_effect_wall + + var/hit_duration + var/hit_duration_wall + +/obj/projectile/hallucination/Initialize(mapload, datum/hallucination/parent) + . = ..() + if(!parent) + stack_trace("[type] was created without a parent hallucination.") + return INITIALIZE_HINT_QDEL + + src.parent = parent + RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/parent_deleting) + + +/obj/projectile/hallucination/Destroy() + if(!QDELETED(parent.hallucinator)) + parent.hallucinator.client?.images -= fake_bullet + fake_bullet = null + + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + parent = null + return ..() + +/// Signal proc for [COMSIG_PARENT_QDELETING], if our associated hallucination deletes, we need to clean up +/obj/projectile/hallucination/proc/parent_deleting(datum/source) + SIGNAL_HANDLER -/datum/hallucination/stray_bullet/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - var/list/turf/startlocs = list() - for(var/turf/open/T in view(world.view+1,target)-view(world.view,target)) - startlocs += T - if(!startlocs.len) - qdel(src) - return - var/turf/start = pick(startlocs) - var/proj_type = pick(subtypesof(/obj/projectile/hallucination)) - feedback_details += "Type: [proj_type]" - var/obj/projectile/hallucination/H = new proj_type(start) - target.playsound_local(start, H.hal_fire_sound, 60, 1) - H.hal_target = target - H.preparePixelProjectile(target, start) - H.fire() qdel(src) + +/obj/projectile/hallucination/fire() + if(hal_fire_sound) + parent.hallucinator.playsound_local(get_turf(src), hal_fire_sound, 60, TRUE) + + fake_bullet = image(hal_icon, src, hal_icon_state, ABOVE_MOB_LAYER) + parent.hallucinator.client?.images += fake_bullet + return ..() + +/obj/projectile/hallucination/on_hit(atom/target, blocked, pierce_hit) + . = ..() + if(. != BULLET_ACT_HIT) + return + + if(ismob(target)) + if(hal_hitsound) + parent.hallucinator.playsound_local(target, hal_hitsound, 100, 1) + on_mob_hit(target) + + else + if(hal_hitsound_wall) + parent.hallucinator.playsound_local(loc, hal_hitsound_wall, 40, 1) + if(hal_impact_effect_wall) + spawn_hit(target, TRUE) + + qdel(src) + return TRUE + +/// Called when a mob is hit by the fake projectile +/obj/projectile/hallucination/proc/on_mob_hit(mob/living/hit_mob) + if(hit_mob == parent.hallucinator) + to_chat(parent.hallucinator, span_userdanger("[hit_mob] is hit by \a [src] in the chest!")) + apply_effect_to_hallucinator(parent.hallucinator) + + else if(hit_mob in view(parent.hallucinator)) + to_chat(parent.hallucinator, span_danger("[hit_mob] is hit by \a [src] in the chest!")) + + if(damage_type == BRUTE) + var/splatter_dir = dir + if(starting) + splatter_dir = get_dir(starting, get_turf(hit_mob)) + spawn_blood(hit_mob, splatter_dir) + + else if(hal_impact_effect) + spawn_hit(hit_mob, FALSE) + +/// Called when the hallucinator themselves are hit by the fake projectile +/obj/projectile/hallucination/proc/apply_effect_to_hallucinator(mob/living/afflicted) + return + +/// Called after a mob is hit by the fake projectile, and our fake projectile is of brute type, to create fake blood +/obj/projectile/hallucination/proc/spawn_blood(mob/living/bleeding, set_dir) + if(!parent.hallucinator.client) // Purely visual, don't need to do this for clientless mobs + return + + var/splatter_icon_state + if(ISDIAGONALDIR(set_dir)) + splatter_icon_state = "splatter[pick(1, 2, 6)]" + else + splatter_icon_state = "splatter[pick(3, 4, 5)]" + + var/image/blood = image('icons/effects/blood.dmi', bleeding, splatter_icon_state, ABOVE_MOB_LAYER) + var/target_pixel_x = 0 + var/target_pixel_y = 0 + switch(set_dir) + if(NORTH) + target_pixel_y = 16 + if(SOUTH) + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + if(EAST) + target_pixel_x = 16 + if(WEST) + target_pixel_x = -16 + if(NORTHEAST) + target_pixel_x = 16 + target_pixel_y = 16 + if(NORTHWEST) + target_pixel_x = -16 + target_pixel_y = 16 + if(SOUTHEAST) + target_pixel_x = 16 + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + if(SOUTHWEST) + target_pixel_x = -16 + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + + parent.hallucinator.client?.images |= blood + animate(blood, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = 5) + addtimer(CALLBACK(src, .proc/clean_up_blood, blood), 0.5 SECONDS) + +/obj/projectile/hallucination/proc/clean_up_blood(image/blood) + parent.hallucinator.client?.images -= blood + +/// Called with a non-mob atom was hit by our fake projectile, or a mob was hit and our damge type is not brute +/obj/projectile/hallucination/proc/spawn_hit(atom/hit_atom, is_wall) + if(!parent.hallucinator.client) // Purely visual, don't need to do this for clientless mobs + return + + var/image/hit_effect = image('icons/effects/blood.dmi', hit_atom, is_wall ? hal_impact_effect_wall : hal_impact_effect, ABOVE_MOB_LAYER) + hit_effect.pixel_x = hit_atom.pixel_x + rand(-4,4) + hit_effect.pixel_y = hit_atom.pixel_y + rand(-4,4) + parent.hallucinator.client.images |= hit_effect + addtimer(CALLBACK(src, .proc/clean_up_hit, hit_effect), is_wall ? hit_duration_wall : hit_duration) + +/obj/projectile/hallucination/proc/clean_up_hit(image/hit_effect) + parent.hallucinator.client?.images -= hit_effect + +/obj/projectile/hallucination/bullet + name = "bullet" + hal_icon_state = "bullet" + hal_fire_sound = "gunshot" + hal_hitsound = 'sound/weapons/pierce.ogg' + hal_hitsound_wall = SFX_RICOCHET + hal_impact_effect = "impact_bullet" + hal_impact_effect_wall = "impact_bullet" + hit_duration = 5 + hit_duration_wall = 5 + +/obj/projectile/hallucination/bullet/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjustStaminaLoss(60) + +/obj/projectile/hallucination/laser + name = "laser" + damage_type = BURN + hal_icon_state = "laser" + hal_fire_sound = 'sound/weapons/laser.ogg' + hal_hitsound = 'sound/weapons/sear.ogg' + hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' + hal_impact_effect = "impact_laser" + hal_impact_effect_wall = "impact_laser_wall" + hit_duration = 4 + hit_duration_wall = 10 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + + ricochets_max = 50 + ricochet_chance = 80 + reflectable = REFLECT_NORMAL // No idea if this works + +/obj/projectile/hallucination/laser/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjustStaminaLoss(20) + afflicted.blur_eyes(2) + +/obj/projectile/hallucination/taser + name = "electrode" + damage_type = BURN + hal_icon_state = "spark" + color = "#FFFF00" + hal_fire_sound = 'sound/weapons/taser.ogg' + hal_hitsound = 'sound/weapons/taserhit.ogg' + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/taser/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.Paralyze(10 SECONDS) + afflicted.adjust_timed_status_effect(40 SECONDS, /datum/status_effect/speech/stutter) + if(HAS_TRAIT(afflicted, TRAIT_HULK)) + afflicted.say(pick( + ";RAAAAAAAARGH!", + ";HNNNNNNNNNGGGGGGH!", + ";GWAAAAAAAARRRHHH!", + "NNNNNNNNGGGGGGGGHH!", + ";AAAAAAARRRGH!"), + forced = "hulk (hallucinating)", + ) + else if((afflicted.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(afflicted, TRAIT_STUNIMMUNE)) + addtimer(CALLBACK(afflicted, /mob/living/carbon.proc/do_jitter_animation, 20), 0.5 SECONDS) + +/obj/projectile/hallucination/disabler + name = "disabler beam" + damage_type = STAMINA + hal_icon_state = "omnilaser" + hal_fire_sound = 'sound/weapons/taser2.ogg' + hal_hitsound = 'sound/weapons/tap.ogg' + hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' + hal_impact_effect = "impact_laser_blue" + hal_impact_effect_wall = null + hit_duration = 4 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + + ricochets_max = 50 + ricochet_chance = 80 + reflectable = REFLECT_NORMAL // No idea if this works + +/obj/projectile/hallucination/disabler/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjustStaminaLoss(30) + +/obj/projectile/hallucination/ebow + name = "bolt" + damage_type = TOX + hal_icon_state = "cbbolt" + hal_fire_sound = 'sound/weapons/genhit.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/ebow/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/speech/slurring) + afflicted.Knockdown(1 SECONDS) + afflicted.adjustStaminaLoss(75) // 60 stam + 15 tox + afflicted.blur_eyes(10) + +/obj/projectile/hallucination/change + name = "bolt of change" + damage_type = BURN + hal_icon_state = "ice_1" + hal_fire_sound = 'sound/magic/staff_change.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/change/apply_effect_to_hallucinator(mob/living/afflicted) + // Future idea: Make it so any other mob hit appear to be polymorphed to the hallucinator + afflicted.cause_hallucination( \ + get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset), \ + "fake [name]", \ + duration = 30 SECONDS, \ + affects_us = TRUE, \ + affects_others = FALSE, \ + skip_nearby = FALSE, \ + play_wabbajack = TRUE, \ + ) + +/obj/projectile/hallucination/death + name = "bolt of death" + damage_type = BURN + hal_icon_state = "pulse1_bl" + hal_fire_sound = 'sound/magic/wandodeath.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/death/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.cause_hallucination(/datum/hallucination/death, "fake [name]") diff --git a/code/modules/hallucination/xeno_attack.dm b/code/modules/hallucination/xeno_attack.dm new file mode 100644 index 00000000000..9cae5550467 --- /dev/null +++ b/code/modules/hallucination/xeno_attack.dm @@ -0,0 +1,103 @@ +/// Xeno crawls from nearby vent, jumps at you, and goes back in. +/datum/hallucination/xeno_attack + random_hallucination_weight = 2 + +/datum/hallucination/xeno_attack/start() + var/turf/xeno_attack_source + for(var/obj/machinery/atmospherics/components/unary/vent_pump/nearby_pump in orange(7, hallucinator)) + if(nearby_pump.welded) + continue + xeno_attack_source = get_turf(nearby_pump) + break + + if(!xeno_attack_source) + return FALSE + + feedback_details += "Vent Coords: ([xeno_attack_source.x], [xeno_attack_source.y], [xeno_attack_source.z])" + + var/obj/effect/client_image_holder/hallucination/xeno/fake_xeno = new(xeno_attack_source, hallucinator, src) + addtimer(CALLBACK(src, .proc/leap_at_target, fake_xeno, xeno_attack_source), 1 SECONDS) + return TRUE + +/// Leaps from the vent to the hallucinator. +/datum/hallucination/xeno_attack/proc/leap_at_target(obj/effect/client_image_holder/hallucination/xeno/fake_xeno, turf/attack_source) + if(QDELETED(src)) + return + if(QDELETED(fake_xeno)) + qdel(src) + return + + fake_xeno.set_leaping() + fake_xeno.throw_at(hallucinator, 7, 1, spin = FALSE, diagonals_first = TRUE) + addtimer(CALLBACK(src, .proc/leap_back_to_pump, fake_xeno), 1 SECONDS) + +/// Leaps from the hallucinator back to the vent. +/datum/hallucination/xeno_attack/proc/leap_back_to_pump(obj/effect/client_image_holder/hallucination/xeno/fake_xeno, turf/attack_source) + if(QDELETED(src)) + return + if(QDELETED(fake_xeno) || !attack_source) + qdel(src) + return + + fake_xeno.set_leaping() + fake_xeno.throw_at(attack_source, 7, 1, spin = FALSE, diagonals_first = TRUE) + addtimer(CALLBACK(src, .proc/begin_crawling, fake_xeno), 1 SECONDS) + +/// Mimics ventcrawling into the vent. +/datum/hallucination/xeno_attack/proc/begin_crawling(obj/effect/client_image_holder/hallucination/xeno/fake_xeno) + if(QDELETED(src)) + return + if(QDELETED(fake_xeno)) + qdel(src) + return + + to_chat(hallucinator, span_notice("[fake_xeno.name] begins climbing into the ventilation system...")) + addtimer(CALLBACK(src, .proc/disappear, fake_xeno), 3 SECONDS) + +/// Disappears into the vent, ending the hallucination. +/datum/hallucination/xeno_attack/proc/disappear(obj/effect/client_image_holder/hallucination/xeno/fake_xeno) + if(QDELETED(src)) + return + if(!QDELETED(fake_xeno)) + to_chat(hallucinator, span_notice("[fake_xeno.name] scrambles into the ventilation ducts!")) + + qdel(src) + +/// The xeno hallucination that goes with the xeno attack hallucination. +/obj/effect/client_image_holder/hallucination/xeno + image_icon = 'icons/mob/nonhuman-player/alien.dmi' + image_state = "alienh_pounce" + +/obj/effect/client_image_holder/hallucination/xeno/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) + . = ..() + name = "alien hunter ([rand(1, 1000)])" + +// The hallucination "throws" us at the hallucinator, so whenever we impact, we're actually landing a "leap". +/obj/effect/client_image_holder/hallucination/xeno/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + set_unleaping() + if(!isliving(hit_atom)) + return + var/mob/living/hit_living = hit_atom + if(hit_living != parent.hallucinator || hit_living.stat == DEAD) + return + hit_living.Paralyze(10 SECONDS) + hit_living.visible_message( + span_warning("[hit_living] flails around wildly."), + span_userdanger("[name] pounces on you!"), + ) + +/// Sets our icon to look like we're leaping. +/obj/effect/client_image_holder/hallucination/xeno/proc/set_leaping() + image_icon = 'icons/mob/nonhuman-player/alienleap.dmi' + image_state = "alienh_leap" + image_pixel_x = -32 + image_pixel_y = -32 + update_appearance(UPDATE_ICON) + +/// Resets our icon to our initial state. +/obj/effect/client_image_holder/hallucination/xeno/proc/set_unleaping() + image_icon = initial(image_icon) + image_state = initial(image_state) + image_pixel_x = initial(image_pixel_x) + image_pixel_y = initial(image_pixel_y) + update_appearance(UPDATE_ICON) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index 41bb1ed8c99..82bda58357e 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -278,8 +278,7 @@ var/mob/living/carbon/wearer = loc if(istype(wearer) && DT_PROB(1, delta_time)) //cursed by bubblegum if(prob(7.5)) - new /datum/hallucination/oh_yeah(wearer) - to_chat(wearer, span_colossus("[pick("I AM IMMORTAL.","I SHALL TAKE BACK WHAT'S MINE.","I SEE YOU.","YOU CANNOT ESCAPE ME FOREVER.","DEATH CANNOT HOLD ME.")]")) + wearer.cause_hallucination(/datum/hallucination/oh_yeah, "H.E.C.K suit", haunt_them = TRUE) else to_chat(wearer, span_warning("[pick("You hear faint whispers.","You smell ash.","You feel hot.","You hear a roar in the distance.")]")) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 6b84aa060a3..8b07f7af0fe 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -231,12 +231,6 @@ return TRUE return FALSE -/mob/living/carbon/hallucinating() - if(hallucination) - return TRUE - else - return FALSE - /mob/living/carbon/resist_buckle() if(HAS_TRAIT(src, TRAIT_RESTRAINED)) changeNext_move(CLICK_CD_BREAKOUT) @@ -740,29 +734,39 @@ clear_fullscreen("brute") /mob/living/carbon/update_health_hud(shown_health_amount) - if(!client || !hud_used) + if(!client || !hud_used?.healths) return - if(hud_used.healths) - if(stat != DEAD) - . = 1 - if(shown_health_amount == null) - shown_health_amount = health - if(shown_health_amount >= maxHealth) - hud_used.healths.icon_state = "health0" - else if(shown_health_amount > maxHealth*0.8) - hud_used.healths.icon_state = "health1" - else if(shown_health_amount > maxHealth*0.6) - hud_used.healths.icon_state = "health2" - else if(shown_health_amount > maxHealth*0.4) - hud_used.healths.icon_state = "health3" - else if(shown_health_amount > maxHealth*0.2) - hud_used.healths.icon_state = "health4" - else if(shown_health_amount > 0) - hud_used.healths.icon_state = "health5" - else - hud_used.healths.icon_state = "health6" - else - hud_used.healths.icon_state = "health7" + + if(stat == DEAD) + hud_used.healths.icon_state = "health7" + return + + if(SEND_SIGNAL(src, COMSIG_CARBON_UPDATING_HEALTH_HUD, shown_health_amount) & COMPONENT_OVERRIDE_HEALTH_HUD) + return + + if(shown_health_amount == null) + shown_health_amount = health + + if(shown_health_amount >= maxHealth) + hud_used.healths.icon_state = "health0" + + else if(shown_health_amount > maxHealth * 0.8) + hud_used.healths.icon_state = "health1" + + else if(shown_health_amount > maxHealth * 0.6) + hud_used.healths.icon_state = "health2" + + else if(shown_health_amount > maxHealth * 0.4) + hud_used.healths.icon_state = "health3" + + else if(shown_health_amount > maxHealth*0.2) + hud_used.healths.icon_state = "health4" + + else if(shown_health_amount > 0) + hud_used.healths.icon_state = "health5" + + else + hud_used.healths.icon_state = "health6" /mob/living/carbon/update_stamina_hud(shown_stamina_amount) if(!client || !hud_used?.stamina) @@ -1004,7 +1008,6 @@ VV_DROPDOWN_OPTION(VV_HK_MAKE_AI, "Make AI") VV_DROPDOWN_OPTION(VV_HK_MODIFY_BODYPART, "Modify bodypart") VV_DROPDOWN_OPTION(VV_HK_MODIFY_ORGANS, "Modify organs") - VV_DROPDOWN_OPTION(VV_HK_HALLUCINATION, "Hallucinate") VV_DROPDOWN_OPTION(VV_HK_MARTIAL_ART, "Give Martial Arts") VV_DROPDOWN_OPTION(VV_HK_GIVE_TRAUMA, "Give Brain Trauma") VV_DROPDOWN_OPTION(VV_HK_CURE_TRAUMA, "Cure Brain Traumas") @@ -1112,18 +1115,6 @@ cure_all_traumas(TRAUMA_RESILIENCE_ABSOLUTE) log_admin("[key_name(usr)] has cured all traumas from [key_name(src)].") message_admins(span_notice("[key_name_admin(usr)] has cured all traumas from [key_name_admin(src)].")) - if(href_list[VV_HK_HALLUCINATION]) - if(!check_rights(NONE)) - return - var/list/hallucinations = subtypesof(/datum/hallucination) - var/result = input(usr, "Choose the hallucination to apply","Send Hallucination") as null|anything in sort_list(hallucinations, /proc/cmp_typepaths_asc) - if(!usr) - return - if(QDELETED(src)) - to_chat(usr, "Mob doesn't exist anymore") - return - if(result) - new result(src, TRUE) /mob/living/carbon/can_resist() return bodyparts.len > 2 && ..() @@ -1131,7 +1122,7 @@ /mob/living/carbon/proc/hypnosis_vulnerable() if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) return FALSE - if(hallucinating()) + if(has_status_effect(/datum/status_effect/hallucination)) return TRUE if(IsSleeping()) return TRUE @@ -1301,9 +1292,6 @@ if(NAMEOF(src, disgust)) set_disgust(var_value) . = TRUE - if(NAMEOF(src, hal_screwyhud)) - set_screwyhud(var_value) - . = TRUE if(NAMEOF(src, handcuffed)) set_handcuffed(var_value) . = TRUE diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 46a939f184f..70d52359108 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -85,9 +85,10 @@ var/list/icon_render_keys = list() var/static/list/limb_icon_cache = list() - //halucination vars - var/hal_screwyhud = SCREWYHUD_NONE - var/next_hallucination = 0 + /// Used to temporarily increase severity of / apply a new damage overlay (the red ring around the ui / screen). + /// This number will translate to equivalent brute or burn damage taken. Handled in [mob/living/proc/update_damage_hud]. + /// (For example, setting damageoverlaytemp = 20 will add 20 "damage" to the overlay the next time it updates.) + /// This number is also reset to 0 every tick of carbon Life(). Pain. var/damageoverlaytemp = 0 ///used to halt stamina regen temporarily diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 90ae446870d..81f6f513562 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -66,7 +66,7 @@ var/temp = getBruteLoss() - if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy + if(!(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy))) //fake healthy if(temp) if (temp < 25) msg += "[t_He] [t_has] minor bruising.\n" diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 6f2b897520e..374c1a0e702 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -171,9 +171,9 @@ else if(l_limbs_missing >= 2 && r_limbs_missing >= 2) msg += "[t_He] [p_do()]n't seem all there.\n" - if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy + if(!(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy))) //fake healthy var/temp - if(user == src && src.hal_screwyhud == SCREWYHUD_CRIT)//fake damage + if(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_crit))//fake damage temp = 50 else temp = getBruteLoss() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 51af1afe950..d1547abc643 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -715,52 +715,46 @@ /mob/living/carbon/human/update_health_hud() if(!client || !hud_used) return - if(dna.species.update_health_hud()) + + // Updates the health bar, also sends signal + . = ..() + + // Updates the health doll + if(!hud_used.healthdoll) return - else - if(hud_used.healths) - if(..()) //not dead - switch(hal_screwyhud) - if(SCREWYHUD_CRIT) - hud_used.healths.icon_state = "health6" - if(SCREWYHUD_DEAD) - hud_used.healths.icon_state = "health7" - if(SCREWYHUD_HEALTHY) - hud_used.healths.icon_state = "health0" - if(hud_used.healthdoll) - hud_used.healthdoll.cut_overlays() - if(stat != DEAD) - hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" - for(var/obj/item/bodypart/body_part as anything in bodyparts) - var/icon_num = 0 - //Hallucinations - if(body_part.type in hal_screwydoll) - icon_num = hal_screwydoll[body_part.type] - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[body_part.body_zone][icon_num]")) - continue - //Not hallucinating - var/damage = body_part.burn_dam + body_part.brute_dam - var/comparison = (body_part.max_damage/5) - if(damage) - icon_num = 1 - if(damage > (comparison)) - icon_num = 2 - if(damage > (comparison*2)) - icon_num = 3 - if(damage > (comparison*3)) - icon_num = 4 - if(damage > (comparison*4)) - icon_num = 5 - if(hal_screwyhud == SCREWYHUD_HEALTHY) - icon_num = 0 - if(icon_num) - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[body_part.body_zone][icon_num]")) - for(var/t in get_missing_limbs()) //Missing limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]6")) - for(var/t in get_disabled_limbs()) //Disabled limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]7")) - else - hud_used.healthdoll.icon_state = "healthdoll_DEAD" + + hud_used.healthdoll.cut_overlays() + if(stat == DEAD) + hud_used.healthdoll.icon_state = "healthdoll_DEAD" + return + + hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" + for(var/obj/item/bodypart/body_part as anything in bodyparts) + var/icon_num = 0 + + if(SEND_SIGNAL(body_part, COMSIG_BODYPART_UPDATING_HEALTH_HUD, src) & COMPONENT_OVERRIDE_BODYPART_HEALTH_HUD) + continue + + var/damage = body_part.burn_dam + body_part.brute_dam + var/comparison = (body_part.max_damage/5) + if(damage) + icon_num = 1 + if(damage > (comparison)) + icon_num = 2 + if(damage > (comparison*2)) + icon_num = 3 + if(damage > (comparison*3)) + icon_num = 4 + if(damage > (comparison*4)) + icon_num = 5 + if(has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy)) + icon_num = 0 + if(icon_num) + hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[body_part.body_zone][icon_num]")) + for(var/t in get_missing_limbs()) //Missing limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]6")) + for(var/t in get_disabled_limbs()) //Disabled limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]7")) /mob/living/carbon/human/fully_heal(admin_revive = FALSE) dna?.species.spec_fully_heal(src) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index ef25cbd4846..7e6927343a9 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -707,77 +707,8 @@ missing -= body_part.body_zone if(body_part.is_pseudopart) //don't show injury text for fake bodyparts; ie chainsaw arms or synthetic armblades continue - var/self_aware = FALSE - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - self_aware = TRUE - var/limb_max_damage = body_part.max_damage - var/status = "" - var/brutedamage = body_part.brute_dam - var/burndamage = body_part.burn_dam - if(hallucination) - if(prob(30)) - brutedamage += rand(30,40) - if(prob(30)) - burndamage += rand(30,40) - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - status = "[brutedamage] brute damage and [burndamage] burn damage" - if(!brutedamage && !burndamage) - status = "no damage" - - else - if(body_part.type in hal_screwydoll)//Are we halucinating? - brutedamage = (hal_screwydoll[body_part.type] * 0.2)*limb_max_damage - - if(brutedamage > 0) - status = body_part.light_brute_msg - if(brutedamage > (limb_max_damage*0.4)) - status = body_part.medium_brute_msg - if(brutedamage > (limb_max_damage*0.8)) - status = body_part.heavy_brute_msg - if(brutedamage > 0 && burndamage > 0) - status += " and " - - if(burndamage > (limb_max_damage*0.8)) - status += body_part.heavy_burn_msg - else if(burndamage > (limb_max_damage*0.2)) - status += body_part.medium_burn_msg - else if(burndamage > 0) - status += body_part.light_burn_msg - - if(status == "") - status = "OK" - var/no_damage - if(status == "OK" || status == "no damage") - no_damage = TRUE - var/isdisabled = "" - if(body_part.bodypart_disabled) - isdisabled = " is disabled" - if(no_damage) - isdisabled += " but otherwise" - else - isdisabled += " and" - combined_msg += "\t Your [body_part.name][isdisabled][self_aware ? " has " : " is "][status]." - - for(var/thing in body_part.wounds) - var/datum/wound/W = thing - var/msg - switch(W.severity) - if(WOUND_SEVERITY_TRIVIAL) - msg = "\t [span_danger("Your [body_part.name] is suffering [W.a_or_from] [lowertext(W.name)].")]" - if(WOUND_SEVERITY_MODERATE) - msg = "\t [span_warning("Your [body_part.name] is suffering [W.a_or_from] [lowertext(W.name)]!")]" - if(WOUND_SEVERITY_SEVERE) - msg = "\t [span_warning("Your [body_part.name] is suffering [W.a_or_from] [lowertext(W.name)]!")]" - if(WOUND_SEVERITY_CRITICAL) - msg = "\t [span_warning("Your [body_part.name] is suffering [W.a_or_from] [lowertext(W.name)]!!")]" - combined_msg += msg - - for(var/obj/item/I in body_part.embedded_objects) - if(I.isEmbedHarmless()) - combined_msg += "\t There is \a [I] stuck to your [body_part.name]!" - else - combined_msg += "\t There is \a [I] embedded in your [body_part.name]!" + body_part.check_for_injuries(src, combined_msg) for(var/t in missing) combined_msg += span_boldannounce("Your [parse_zone(t)] is missing!") diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index e3413b721fa..6dab0bf97e7 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -82,7 +82,5 @@ ///Exposure to damaging heat levels increases stacks, stacks clean over time when temperatures are lower. Stack is consumed to add a wound. var/heat_exposure_stacks = 0 - ///human specific screwyhuds from hallucinations (define key (bodypart) to int value (severity)) - see /datum/hallucination/fake_health_doll - var/hal_screwydoll /// When an braindead player has their equipment fiddled with, we log that info here for when they come back so they know who took their ID while they were DC'd for 30 seconds var/list/afk_thefts diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index ef225715734..4f0362bc8ce 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -981,7 +981,13 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/species/proc/pre_equip_species_outfit(datum/job/job, mob/living/carbon/human/equipping, visuals_only = FALSE) return - +/** + * Handling special reagent types. + * + * Return False to run the normal on_mob_life() for that reagent. + * Return True to not run the normal metabolism effects. + * NOTE: If you return TRUE, that reagent will not be removed liike normal! You must handle it manually. + */ /datum/species/proc/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H, delta_time, times_fired) if(chem.type == exotic_blood) H.blood_volume = min(H.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM) @@ -1005,9 +1011,6 @@ GLOBAL_LIST_EMPTY(features_by_species) outfit_important_for_life= new() outfit_important_for_life.equip(human_to_equip) -/datum/species/proc/update_health_hud(mob/living/carbon/human/H) - return FALSE - /** * Species based handling for irradiation * diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 1a98954eb41..ae4014f3976 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -138,6 +138,7 @@ var/datum/wound/iter_wound = i iter_wound.on_xadone(4 * REAGENTS_EFFECT_MULTIPLIER * delta_time) // plasmamen use plasma to reform their bones or whatever return TRUE + if(istype(chem, /datum/reagent/toxin/bonehurtingjuice)) H.adjustStaminaLoss(7.5 * REAGENTS_EFFECT_MULTIPLIER * delta_time, 0) H.adjustBruteLoss(0.5 * REAGENTS_EFFECT_MULTIPLIER * delta_time, 0) @@ -164,6 +165,13 @@ H.reagents.remove_reagent(chem.type, chem.metabolization_rate * delta_time) return TRUE + if(istype(chem, /datum/reagent/gunpowder)) + H.set_timed_status_effect(15 SECONDS * delta_time, /datum/status_effect/drugginess) + if(H.get_timed_status_effect_duration(/datum/status_effect/hallucination) / 10 < chem.volume) + H.adjust_hallucinations(2.5 SECONDS * delta_time) + // Do normal metabolism + return FALSE + /datum/species/plasmaman/get_species_description() return "Found on the Icemoon of Freyja, plasmamen consist of colonial \ fungal organisms which together form a sentient being. In human space, \ diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 6d91adc4f19..8250b759139 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -239,9 +239,9 @@ if(breath_gases[/datum/gas/bz]) var/bz_partialpressure = (breath_gases[/datum/gas/bz][MOLES]/breath.total_moles())*breath_pressure if(bz_partialpressure > 1) - hallucination += 10 + adjust_hallucinations(20 SECONDS) else if(bz_partialpressure > 0.01) - hallucination += 5 + adjust_hallucinations(10 SECONDS) //NITRIUM if(breath_gases[/datum/gas/nitrium]) @@ -413,9 +413,6 @@ if(silent) silent = max(silent - (0.5 * delta_time), 0) - if(hallucination) - handle_hallucinations(delta_time, times_fired) - /// Base carbon environment handler, adds natural stabilization /mob/living/carbon/handle_environment(datum/gas_mixture/environment, delta_time, times_fired) var/areatemp = get_temperature(environment) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 0cc307c58bc..a293e6853e7 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -832,7 +832,6 @@ cure_nearsighted() cure_blind() cure_husk() - hallucination = 0 heal_overall_damage(INFINITY, INFINITY, INFINITY, null, TRUE) //heal brute and burn dmg on both organic and robotic limbs, and update health right away. extinguish_mob() set_drowsyness(0) @@ -1743,6 +1742,8 @@ GLOBAL_LIST_EMPTY(fire_appearances) VV_DROPDOWN_OPTION(VV_HK_GIVE_SPEECH_IMPEDIMENT, "Impede Speech (Slurring, stuttering, etc)") VV_DROPDOWN_OPTION(VV_HK_ADD_MOOD, "Add Mood Event") VV_DROPDOWN_OPTION(VV_HK_REMOVE_MOOD, "Remove Mood Event") + VV_DROPDOWN_OPTION(VV_HK_GIVE_HALLUCINATION, "Give Hallucination") + VV_DROPDOWN_OPTION(VV_HK_GIVE_DELUSION_HALLUCINATION, "Give Delusion Hallucination") /mob/living/vv_do_topic(list/href_list) . = ..() @@ -1756,6 +1757,16 @@ GLOBAL_LIST_EMPTY(fire_appearances) if (href_list[VV_HK_REMOVE_MOOD]) admin_remove_mood_event(usr) + if(href_list[VV_HK_GIVE_HALLUCINATION]) + if(!check_rights(NONE)) + return + admin_give_hallucination(usr) + + if(href_list[VV_HK_GIVE_DELUSION_HALLUCINATION]) + if(!check_rights(NONE)) + return + admin_give_delusion(usr) + /mob/living/proc/move_to_error_room() var/obj/effect/landmark/error/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list if(error_landmark) @@ -2341,3 +2352,34 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/played_game() . = ..() add_mood_event("gaming", /datum/mood_event/gaming) + +/// Admin only proc for making the mob hallucinate a certain thing +/mob/living/proc/admin_give_hallucination(mob/admin) + if(!admin || !check_rights(NONE)) + return + + var/chosen = select_hallucination_type(admin, "What hallucination do you want to give to [src]?", "Give Hallucination") + if(!chosen || QDELETED(src) || !check_rights(NONE)) + return + + if(!cause_hallucination(chosen, "admin forced by [key_name_admin(admin)]")) + to_chat(admin, "That hallucination ([chosen]) could not be run - it may be invalid with this type of mob or has no effects.") + return + + message_admins("[key_name_admin(admin)] gave [ADMIN_LOOKUPFLW(src)] a hallucination. (Type: [chosen])") + log_admin("[key_name(admin)] gave [src] a hallucination. (Type: [chosen])") + +/// Admin only proc for giving the mob a delusion hallucination with specific arguments +/mob/living/proc/admin_give_delusion(mob/admin) + if(!admin || !check_rights(NONE)) + return + + var/list/delusion_args = create_delusion(admin) + if(QDELETED(src) || !check_rights(NONE) || !length(delusion_args)) + return + + delusion_args[2] = "admin forced" + message_admins("[key_name_admin(admin)] gave [ADMIN_LOOKUPFLW(src)] a delusion hallucination. (Type: [delusion_args[1]])") + log_admin("[key_name(admin)] gave [src] a delusion hallucination. (Type: [delusion_args[1]])") + // Not using the wrapper here because we already have a list / arglist + _cause_hallucination(delusion_args) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 43e92675367..6a2ba37dedd 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -63,10 +63,12 @@ diag_hud_set_health() add_sensors() ADD_TRAIT(src, TRAIT_ADVANCEDTOOLUSER, ROUNDSTART_TRAIT) - ADD_TRAIT(src, TRAIT_MARTIAL_ARTS_IMMUNE, ROUNDSTART_TRAIT) - ADD_TRAIT(src, TRAIT_NOFIRE_SPREAD, ROUNDSTART_TRAIT) - ADD_TRAIT(src, TRAIT_ASHSTORM_IMMUNE, ROUNDSTART_TRAIT) ADD_TRAIT(src, TRAIT_LITERATE, ROUNDSTART_TRAIT) + ADD_TRAIT(src, TRAIT_NOFIRE_SPREAD, ROUNDSTART_TRAIT) + + ADD_TRAIT(src, TRAIT_ASHSTORM_IMMUNE, ROUNDSTART_TRAIT) + ADD_TRAIT(src, TRAIT_MADNESS_IMMUNE, ROUNDSTART_TRAIT) + ADD_TRAIT(src, TRAIT_MARTIAL_ARTS_IMMUNE, ROUNDSTART_TRAIT) /mob/living/silicon/Destroy() QDEL_NULL(radio) diff --git a/code/modules/mob/living/simple_animal/guardian/types/fire.dm b/code/modules/mob/living/simple_animal/guardian/types/fire.dm index fa53d7208ea..44a357e467b 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/fire.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/fire.dm @@ -29,8 +29,24 @@ /mob/living/simple_animal/hostile/guardian/fire/AttackingTarget() . = ..() - if(. && ishuman(target) && target != summoner) - new /datum/hallucination/delusion(target,TRUE,"custom",200,0, icon_state,icon) + if(!.) + return + if(!isliving(target)) + return + if(target == summoner) + return + var/mob/living/living_target = target + living_target.cause_hallucination( \ + /datum/hallucination/delusion/custom, \ + "fire holoparasite ([key_name(src)], owned by [key_name(summoner)])", \ + duration = 20 SECONDS, \ + affects_us = TRUE, \ + affects_others = TRUE, \ + skip_nearby = FALSE, \ + play_wabbajack = FALSE, \ + custom_icon_file = icon, \ + custom_icon_state = icon_state, \ + ) /mob/living/simple_animal/hostile/guardian/fire/proc/on_entered(datum/source, AM as mob|obj) SIGNAL_HANDLER diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm index 95702e6ae69..a4597b2ff67 100644 --- a/code/modules/mob/living/taste.dm +++ b/code/modules/mob/living/taste.dm @@ -32,7 +32,7 @@ var/text_output = from.generate_taste_message(src, taste_sensitivity) // We dont want to spam the same message over and over again at the // person. Give it a bit of a buffer. - if(hallucination > 50 && prob(25)) + if(get_timed_status_effect_duration(/datum/status_effect/hallucination) > 100 SECONDS && prob(25)) text_output = pick("spiders","dreams","nightmares","the future","the past","victory",\ "defeat","pain","bliss","revenge","poison","time","space","death","life","truth","lies","justice","memory",\ "regrets","your soul","suffering","music","noise","blood","hunger","the american way") diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 8a15f935dc6..19578f74ff7 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -211,11 +211,6 @@ SHOULD_BE_PURE(TRUE) return eye_blind ? TRUE : HAS_TRAIT(src, TRAIT_BLIND) -///Is the mob hallucinating? -/mob/proc/hallucinating() - return FALSE - - // moved out of admins.dm because things other than admin procs were calling this. /// Returns TRUE if the game has started and we're either an AI with a 0th law, or we're someone with a special role/antag datum /proc/is_special_character(mob/M) diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm index 433535d47aa..d3625e10148 100644 --- a/code/modules/plumbing/plumbers/synthesizer.dm +++ b/code/modules/plumbing/plumbers/synthesizer.dm @@ -62,7 +62,10 @@ /obj/machinery/plumbing/synthesizer/ui_data(mob/user) var/list/data = list() - var/is_hallucinating = user.hallucinating() + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) var/list/chemicals = list() for(var/A in dispensable_reagents) diff --git a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm index 2345d809baf..07b5142b93b 100644 --- a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm +++ b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm @@ -23,10 +23,10 @@ continue if(victim.z == 0) continue - if(ishuman(victim)) - //Hilariously enough, running into a closet should make you get hit the hardest. - var/mob/living/carbon/human/human = victim - human.hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(victim, sm) + 1)) ) ) + + //Hilariously enough, running into a closet should make you get hit the hardest. + var/hallucination_amount = max(100 SECONDS, min(600 SECONDS, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(victim, src) + 1)))) + victim.adjust_hallucinations(hallucination_amount) for(var/mob/victim as anything in GLOB.player_list) var/turf/victim_turf = get_turf(victim) diff --git a/code/modules/power/supermatter/supermatter_process.dm b/code/modules/power/supermatter/supermatter_process.dm index 504cefed5b0..4a360557b7a 100644 --- a/code/modules/power/supermatter/supermatter_process.dm +++ b/code/modules/power/supermatter/supermatter_process.dm @@ -257,24 +257,17 @@ // no supermatter soothers are nearby. var/psy_coeff_diff = -0.05 for(var/mob/living/carbon/human/seen_by_sm in view(src, HALLUCINATION_RANGE(power))) - // Someone (generally a Psychologist), when looking at the SM - // within hallucination range makes it easier to manage. + // Someone (generally a Psychologist), when looking at the SM within hallucination range makes it easier to manage. if(HAS_TRAIT(seen_by_sm, TRAIT_SUPERMATTER_SOOTHER) || (seen_by_sm.mind && HAS_TRAIT(seen_by_sm.mind, TRAIT_SUPERMATTER_SOOTHER))) psy_coeff_diff = 0.05 psy_overlay = TRUE - // If they are immune to supermatter hallucinations. - if (HAS_TRAIT(seen_by_sm, TRAIT_MADNESS_IMMUNE) || (seen_by_sm.mind && HAS_TRAIT(seen_by_sm.mind, TRAIT_MADNESS_IMMUNE))) - continue - - // Blind people don't get supermatter hallucinations. - if (seen_by_sm.is_blind()) - continue - - // Everyone else gets hallucinations. - var/dist = sqrt(1 / max(1, get_dist(seen_by_sm, src))) - seen_by_sm.hallucination += power * hallucination_power * dist - seen_by_sm.hallucination = clamp(seen_by_sm.hallucination, 0, 200) + visible_hallucination_pulse( + center = src, + radius = HALLUCINATION_RANGE(power), + hallucination_duration = power * hallucination_power, + hallucination_max_duration = 400 SECONDS, + ) psyCoeff = clamp(psyCoeff + psy_coeff_diff, 0, 1) /obj/machinery/power/supermatter_crystal/proc/handle_high_power(datum/gas_mixture/removed) diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm deleted file mode 100644 index d2978ead3dd..00000000000 --- a/code/modules/projectiles/projectile/special/hallucination.dm +++ /dev/null @@ -1,230 +0,0 @@ -/obj/projectile/hallucination - name = "bullet" - icon = null - icon_state = null - hitsound = "" - suppressed = TRUE - ricochets_max = 0 - ricochet_chance = 0 - damage = 0 - nodamage = TRUE - projectile_type = /obj/projectile/hallucination - log_override = TRUE - var/hal_icon_state - var/image/fake_icon - var/mob/living/carbon/hal_target - var/hal_fire_sound - var/hal_hitsound - var/hal_hitsound_wall - var/hal_impact_effect - var/hal_impact_effect_wall - var/hit_duration - var/hit_duration_wall - -/obj/projectile/hallucination/fire() - ..() - fake_icon = image('icons/obj/weapons/guns/projectiles.dmi', src, hal_icon_state, ABOVE_MOB_LAYER) - if(hal_target.client) - hal_target.client.images += fake_icon - -/obj/projectile/hallucination/Destroy() - if(hal_target?.client) - hal_target.client.images -= fake_icon - QDEL_NULL(fake_icon) - return ..() - -/obj/projectile/hallucination/Bump(atom/A) - if(!ismob(A)) - if(hal_hitsound_wall) - hal_target.playsound_local(loc, hal_hitsound_wall, 40, 1) - if(hal_impact_effect_wall) - spawn_hit(A, TRUE) - else if(A == hal_target) - if(hal_hitsound) - hal_target.playsound_local(A, hal_hitsound, 100, 1) - target_on_hit(A) - qdel(src) - return TRUE - -/obj/projectile/hallucination/proc/target_on_hit(mob/M) - if(M == hal_target) - to_chat(hal_target, span_userdanger("[M] is hit by \a [src] in the chest!")) - hal_apply_effect() - else if(M in view(hal_target)) - to_chat(hal_target, span_danger("[M] is hit by \a [src] in the chest!!")) - if(damage_type == BRUTE) - var/splatter_dir = dir - if(starting) - splatter_dir = get_dir(starting, get_turf(M)) - spawn_blood(M, splatter_dir) - else if(hal_impact_effect) - spawn_hit(M, FALSE) - -/obj/projectile/hallucination/proc/spawn_blood(mob/M, set_dir) - set waitfor = 0 - if(!hal_target.client) - return - - var/splatter_icon_state - if(ISDIAGONALDIR(set_dir)) - splatter_icon_state = "splatter[pick(1, 2, 6)]" - else - splatter_icon_state = "splatter[pick(3, 4, 5)]" - - var/image/blood = image('icons/effects/blood.dmi', M, splatter_icon_state, ABOVE_MOB_LAYER) - var/target_pixel_x = 0 - var/target_pixel_y = 0 - switch(set_dir) - if(NORTH) - target_pixel_y = 16 - if(SOUTH) - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - if(EAST) - target_pixel_x = 16 - if(WEST) - target_pixel_x = -16 - if(NORTHEAST) - target_pixel_x = 16 - target_pixel_y = 16 - if(NORTHWEST) - target_pixel_x = -16 - target_pixel_y = 16 - if(SOUTHEAST) - target_pixel_x = 16 - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - if(SOUTHWEST) - target_pixel_x = -16 - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - hal_target.client.images += blood - animate(blood, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = 5) - addtimer(CALLBACK(src, .proc/cleanup_blood), 5) - -/obj/projectile/hallucination/proc/cleanup_blood(image/blood) - hal_target.client.images -= blood - qdel(blood) - -/obj/projectile/hallucination/proc/spawn_hit(atom/A, is_wall) - set waitfor = 0 - if(!hal_target.client) - return - - var/image/hit_effect = image('icons/effects/blood.dmi', A, is_wall ? hal_impact_effect_wall : hal_impact_effect, ABOVE_MOB_LAYER) - hit_effect.pixel_x = A.pixel_x + rand(-4,4) - hit_effect.pixel_y = A.pixel_y + rand(-4,4) - hal_target.client.images += hit_effect - sleep(is_wall ? hit_duration_wall : hit_duration) - hal_target.client.images -= hit_effect - qdel(hit_effect) - - -/obj/projectile/hallucination/proc/hal_apply_effect() - return - -/obj/projectile/hallucination/bullet - name = "bullet" - hal_icon_state = "bullet" - hal_fire_sound = "gunshot" - hal_hitsound = 'sound/weapons/pierce.ogg' - hal_hitsound_wall = SFX_RICOCHET - hal_impact_effect = "impact_bullet" - hal_impact_effect_wall = "impact_bullet" - hit_duration = 5 - hit_duration_wall = 5 - -/obj/projectile/hallucination/bullet/hal_apply_effect() - hal_target.adjustStaminaLoss(60) - -/obj/projectile/hallucination/laser - name = "laser" - damage_type = BURN - hal_icon_state = "laser" - hal_fire_sound = 'sound/weapons/laser.ogg' - hal_hitsound = 'sound/weapons/sear.ogg' - hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' - hal_impact_effect = "impact_laser" - hal_impact_effect_wall = "impact_laser_wall" - hit_duration = 4 - hit_duration_wall = 10 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/projectile/hallucination/laser/hal_apply_effect() - hal_target.adjustStaminaLoss(20) - hal_target.blur_eyes(2) - -/obj/projectile/hallucination/taser - name = "electrode" - damage_type = BURN - hal_icon_state = "spark" - color = "#FFFF00" - hal_fire_sound = 'sound/weapons/taser.ogg' - hal_hitsound = 'sound/weapons/taserhit.ogg' - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/taser/hal_apply_effect() - hal_target.Paralyze(100) - hal_target.adjust_timed_status_effect(40 SECONDS, /datum/status_effect/speech/stutter) - if(hal_target.dna && hal_target.dna.check_mutation(/datum/mutation/human/hulk)) - hal_target.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") - else if((hal_target.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(hal_target, TRAIT_STUNIMMUNE)) - addtimer(CALLBACK(hal_target, /mob/living/carbon.proc/do_jitter_animation, 20), 5) - -/obj/projectile/hallucination/disabler - name = "disabler beam" - damage_type = STAMINA - hal_icon_state = "omnilaser" - hal_fire_sound = 'sound/weapons/taser2.ogg' - hal_hitsound = 'sound/weapons/tap.ogg' - hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' - hal_impact_effect = "impact_laser_blue" - hal_impact_effect_wall = null - hit_duration = 4 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/projectile/hallucination/disabler/hal_apply_effect() - hal_target.adjustStaminaLoss(25) - -/obj/projectile/hallucination/ebow - name = "bolt" - damage_type = TOX - hal_icon_state = "cbbolt" - hal_fire_sound = 'sound/weapons/genhit.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/ebow/hal_apply_effect() - hal_target.Paralyze(100) - hal_target.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/speech/stutter) - hal_target.adjustStaminaLoss(8) - -/obj/projectile/hallucination/change - name = "bolt of change" - damage_type = BURN - hal_icon_state = "ice_1" - hal_fire_sound = 'sound/magic/staff_change.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/change/hal_apply_effect() - new /datum/hallucination/self_delusion(hal_target, TRUE, wabbajack = FALSE) - -/obj/projectile/hallucination/death - name = "bolt of death" - damage_type = BURN - hal_icon_state = "pulse1_bl" - hal_fire_sound = 'sound/magic/wandodeath.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/death/hal_apply_effect() - new /datum/hallucination/death(hal_target, TRUE) diff --git a/code/modules/projectiles/projectile/special/mindflayer.dm b/code/modules/projectiles/projectile/special/mindflayer.dm index d91735cb199..54889bbced1 100644 --- a/code/modules/projectiles/projectile/special/mindflayer.dm +++ b/code/modules/projectiles/projectile/special/mindflayer.dm @@ -4,6 +4,6 @@ /obj/projectile/beam/mindflayer/on_hit(atom/target, blocked = FALSE) . = ..() if(ishuman(target)) - var/mob/living/carbon/human/M = target - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20) - M.hallucination += 30 + var/mob/living/carbon/human/human_hit = target + human_hit.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20) + human_hit.adjust_hallucinations(60 SECONDS) diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index c31b120b986..e29cb19e72d 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -728,7 +728,7 @@ owner = reagent.holder.my_atom if(owner && reagent) - if(!owner.reagent_check(reagent, delta_time, times_fired) != TRUE) + if(owner.reagent_check(reagent, delta_time, times_fired)) return if(liverless && !reagent.self_consuming) //need to be metabolized return diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index b682aed7929..505de4cdc2e 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -188,8 +188,15 @@ ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "ChemDispenser", name) - if(user.hallucinating()) + + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + + if(is_hallucinating) ui.set_autoupdate(FALSE) //to not ruin the immersion by constantly changing the fake chemicals + ui.open() /obj/machinery/chem_dispenser/ui_data(mob/user) @@ -221,8 +228,10 @@ var/chemicals[0] var/is_hallucinating = FALSE - if(user.hallucinating()) - is_hallucinating = TRUE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + for(var/re in dispensable_reagents) var/datum/reagent/temp = GLOB.chemical_reagents_list[re] if(temp) diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 129448def45..ea4cb6c4915 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -203,12 +203,11 @@ All effects don't start immediately, but rather get worse over time; the rate is taste_description = "pancake syrup" glass_name = "glass of candy corn liquor" glass_desc = "Good for your Imagination." - var/hal_amt = 4 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/whiskey/candycorn/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) if(DT_PROB(5, delta_time)) - drinker.hallucination += hal_amt //conscious dreamers can be treasurers to their own currency + drinker.adjust_hallucinations(4 SECONDS * REM * delta_time) ..() /datum/reagent/consumable/ethanol/thirteenloko @@ -467,7 +466,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/absinthe/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) if(DT_PROB(5, delta_time) && !HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) - drinker.hallucination += 4 //Reference to the urban myth + drinker.adjust_hallucinations(8 SECONDS) ..() /datum/reagent/consumable/ethanol/hooch @@ -801,13 +800,14 @@ All effects don't start immediately, but rather get worse over time; the rate is var/obj/item/organ/internal/liver/liver = drinker.getorganslot(ORGAN_SLOT_LIVER) // if you have a liver and that liver is an officer's liver if(liver && HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) + . = TRUE drinker.adjustStaminaLoss(-10 * REM * delta_time, 0) if(DT_PROB(10, delta_time)) - new /datum/hallucination/items_other(drinker) + drinker.cause_hallucination(get_random_valid_hallucination_subtype(/datum/hallucination/nearby_fake_item), name) if(DT_PROB(5, delta_time)) - new /datum/hallucination/stray_bullet(drinker) + drinker.cause_hallucination(/datum/hallucination/stray_bullet, name) + ..() - . = TRUE /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_end_metabolize(mob/living/carbon/drinker) if(beepsky_hallucination) @@ -2649,10 +2649,17 @@ All effects don't start immediately, but rather get worse over time; the rate is chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/drunken_espatier/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) - drinker.hal_screwyhud = SCREWYHUD_HEALTHY //almost makes you forget how much it hurts drinker.add_mood_event("numb", /datum/mood_event/narcotic_medium, name) //comfortably numb ..() +/datum/reagent/consumable/ethanol/drunken_espatier/on_mob_metabolize(mob/living/drinker) + . = ..() + drinker.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/reagent/consumable/ethanol/drunken_espatier/on_mob_end_metabolize(mob/living/drinker) + . = ..() + drinker.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + /datum/reagent/consumable/ethanol/protein_blend name = "Protein Blend" description = "A vile blend of protein, pure grain alcohol, korta flour, and blood. Useful for bulking up, if you can keep it down." @@ -2897,8 +2904,9 @@ All effects don't start immediately, but rather get worse over time; the rate is chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/helianthus/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) - if(drinker.hallucination < hal_cap && DT_PROB(5, delta_time)) - drinker.hallucination += hal_amt + if(DT_PROB(5, delta_time)) + drinker.adjust_hallucinations_up_to(4 SECONDS * REM * delta_time, 48 SECONDS) + ..() /datum/reagent/consumable/ethanol/plumwine @@ -2930,7 +2938,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/gin_garden name = "Gin Garden" description = "Excellent cooling alcoholic drink with not so ordinary taste." - color = "#6cd87a" + color = "#6cd87a" taste_description = "light gin with sweet ginger and cucumber" glass_icon_state = "gin_garden" glass_name = "gin garden" diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 263fa75918b..eaf564dd4cf 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -30,8 +30,9 @@ M.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name) /datum/reagent/drug/space_drugs/overdose_process(mob/living/M, delta_time, times_fired) - if(M.hallucination < volume && DT_PROB(10, delta_time)) - M.hallucination += 5 + var/hallucination_duration_in_seconds = (M.get_timed_status_effect_duration(/datum/status_effect/hallucination) / 10) + if(hallucination_duration_in_seconds < volume && DT_PROB(10, delta_time)) + M.adjust_hallucinations(10 SECONDS) ..() /datum/reagent/drug/cannabis @@ -220,7 +221,7 @@ M.add_mood_event("salted", /datum/mood_event/stimulant_heavy, name) M.adjustStaminaLoss(-5 * REM * delta_time, 0) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4 * REM * delta_time) - M.hallucination += 5 * REM * delta_time + M.adjust_hallucinations(10 SECONDS * REM * delta_time) if(!HAS_TRAIT(M, TRAIT_IMMOBILIZED) && !ismovable(M.loc)) step(M, pick(GLOB.cardinals)) step(M, pick(GLOB.cardinals)) @@ -228,7 +229,7 @@ . = TRUE /datum/reagent/drug/bath_salts/overdose_process(mob/living/M, delta_time, times_fired) - M.hallucination += 5 * REM * delta_time + M.adjust_hallucinations(10 SECONDS * REM * delta_time) if(!HAS_TRAIT(M, TRAIT_IMMOBILIZED) && !ismovable(M.loc)) for(var/i in 1 to round(8 * REM * delta_time, 1)) step(M, pick(GLOB.cardinals)) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 25838d00247..710a54f85bb 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -87,7 +87,7 @@ for(var/effect in typesof(/datum/status_effect/speech)) M.remove_status_effect(effect) M.remove_status_effect(/datum/status_effect/jitter) - M.hallucination = 0 + M.remove_status_effect(/datum/status_effect/hallucination) REMOVE_TRAITS_NOT_IN(M, list(SPECIES_TRAIT, ROUNDSTART_TRAIT, ORGAN_TRAIT)) M.reagents.remove_all_type(/datum/reagent/toxin, 5 * REM * delta_time, FALSE, TRUE) if(M.blood_volume < BLOOD_VOLUME_NORMAL) @@ -125,7 +125,7 @@ M.AdjustParalyzed(-20 * REM * delta_time) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5 * REM * delta_time) - M.hallucination = max(M.hallucination - (10 * REM * delta_time), 0) + M.adjust_hallucinations(-20 SECONDS * REM * delta_time) if(DT_PROB(16, delta_time)) M.adjustToxLoss(1, 0) . = TRUE @@ -144,7 +144,7 @@ holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5 * REM * delta_time) if(holder.has_reagent(/datum/reagent/toxin/histamine)) holder.remove_reagent(/datum/reagent/toxin/histamine, 5 * REM * delta_time) - M.hallucination = max(M.hallucination - (10 * REM * delta_time), 0) + M.adjust_hallucinations(-20 SECONDS * REM * delta_time) if(DT_PROB(16, delta_time)) M.adjustToxLoss(1, 0) . = TRUE @@ -355,7 +355,6 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/C, delta_time, times_fired) - C.hal_screwyhud = SCREWYHUD_HEALTHY C.adjustBruteLoss(-0.25 * REM * delta_time, 0) C.adjustFireLoss(-0.25 * REM * delta_time, 0) ..() @@ -380,11 +379,13 @@ if(show_message) to_chat(exposed_carbon, span_danger("You feel your injuries fade away to nothing!") ) -/datum/reagent/medicine/mine_salve/on_mob_end_metabolize(mob/living/M) - if(iscarbon(M)) - var/mob/living/carbon/N = M - N.hal_screwyhud = SCREWYHUD_NONE - ..() +/datum/reagent/medicine/mine_salve/on_mob_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/reagent/medicine/mine_salve/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) /datum/reagent/medicine/omnizine name = "Omnizine" @@ -1160,7 +1161,7 @@ ..() /datum/reagent/medicine/earthsblood/overdose_process(mob/living/M, delta_time, times_fired) - M.hallucination = clamp(M.hallucination + (5 * REM * delta_time), 0, 60) + M.adjust_hallucinations_up_to(10 SECONDS * REM * delta_time, 120 SECONDS) if(current_cycle > 25) M.adjustToxLoss(4 * REM * delta_time, 0) if(current_cycle > 100) //podpeople get out reeeeeeeeeeeeeeeeeeeee @@ -1189,8 +1190,9 @@ if(M.get_timed_status_effect_duration(/datum/status_effect/jitter) >= 6 SECONDS) M.adjust_timed_status_effect(-6 SECONDS * REM * delta_time, /datum/status_effect/jitter) - if (M.hallucination >= 5) - M.hallucination -= 5 * REM * delta_time + if (M.get_timed_status_effect_duration(/datum/status_effect/hallucination) >= 10 SECONDS) + M.adjust_hallucinations(-10 SECONDS * REM * delta_time) + if(DT_PROB(10, delta_time)) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 50) M.adjustStaminaLoss(2.5 * REM * delta_time, 0) @@ -1393,7 +1395,7 @@ . = TRUE /datum/reagent/medicine/psicodine/overdose_process(mob/living/M, delta_time, times_fired) - M.hallucination = clamp(M.hallucination + (5 * REM * delta_time), 0, 60) + M.adjust_hallucinations_up_to(10 SECONDS * REM * delta_time, 120 SECONDS) M.adjustToxLoss(1 * REM * delta_time, 0) ..() . = TRUE diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 3ed55f3d988..41eb088e823 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -112,15 +112,6 @@ UnregisterSignal(holder.my_atom, COMSIG_ATOM_EX_ACT) return ..() -/datum/reagent/gunpowder/on_mob_life(mob/living/carbon/M, delta_time, times_fired) - . = TRUE - ..() - if(!isplasmaman(M)) - return - M.set_timed_status_effect(30 SECONDS * REM * delta_time, /datum/status_effect/drugginess) - if(M.hallucination < volume) - M.hallucination += 5 * REM * delta_time - /datum/reagent/gunpowder/proc/on_ex_act(atom/source, severity, target) SIGNAL_HANDLER if(source.flags_1 & PREVENT_CONTENTS_EXPLOSION_1) diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index a9bb78ee430..ecb0f88f97d 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -306,11 +306,23 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/hallucinogens = 18) //7.2 per 2 seconds -/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/M, delta_time, times_fired) - if(HAS_TRAIT(M, TRAIT_INSANITY)) - M.hallucination = 0 + +/datum/reagent/toxin/mindbreaker/on_mob_metabolize(mob/living/metabolizer) + . = ..() + ADD_TRAIT(metabolizer, TRAIT_HALLUCINATION_SUPPRESSED, type) + +/datum/reagent/toxin/mindbreaker/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + REMOVE_TRAIT(metabolizer, TRAIT_HALLUCINATION_SUPPRESSED, type) + +/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/metabolizer, delta_time, times_fired) + // mindbreaker toxin assuages hallucinations in those plagued with it, mentally + if(metabolizer.has_trauma_type(/datum/brain_trauma/mild/hallucinations)) + metabolizer.remove_status_effect(/datum/status_effect/hallucination) + + // otherwise it creates hallucinations. truly a miracle medicine. else - M.hallucination += 5 * REM * delta_time + metabolizer.adjust_hallucinations(10 SECONDS * REM * delta_time) return ..() diff --git a/code/modules/reagents/withdrawal/generic_addictions.dm b/code/modules/reagents/withdrawal/generic_addictions.dm index 7cb4564ae94..f8e0c8cbb87 100644 --- a/code/modules/reagents/withdrawal/generic_addictions.dm +++ b/code/modules/reagents/withdrawal/generic_addictions.dm @@ -58,12 +58,12 @@ /datum/addiction/alcohol/withdrawal_stage_2_process(mob/living/carbon/affected_carbon, delta_time) . = ..() affected_carbon.set_timed_status_effect(20 SECONDS * delta_time, /datum/status_effect/jitter, only_if_higher = TRUE) - affected_carbon.hallucination = max(5 SECONDS, affected_carbon.hallucination) + affected_carbon.set_hallucinations_if_lower(10 SECONDS) /datum/addiction/alcohol/withdrawal_stage_3_process(mob/living/carbon/affected_carbon, delta_time) . = ..() affected_carbon.set_timed_status_effect(30 SECONDS * delta_time, /datum/status_effect/jitter, only_if_higher = TRUE) - affected_carbon.hallucination = max(5 SECONDS, affected_carbon.hallucination) + affected_carbon.set_hallucinations_if_lower(10 SECONDS) if(DT_PROB(4, delta_time)) if(!HAS_TRAIT(affected_carbon, TRAIT_ANTICONVULSANT)) affected_carbon.apply_status_effect(/datum/status_effect/seizure) @@ -96,7 +96,7 @@ /datum/addiction/maintenance_drugs/withdrawal_enters_stage_1(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_HEALTHY + affected_carbon.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) /datum/addiction/maintenance_drugs/withdrawal_stage_1_process(mob/living/carbon/affected_carbon, delta_time) . = ..() @@ -143,7 +143,7 @@ /datum/addiction/maintenance_drugs/end_withdrawal(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_NONE + affected_carbon.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) if(!ishuman(affected_carbon)) return var/mob/living/carbon/human/affected_human = affected_carbon @@ -158,15 +158,24 @@ /datum/addiction/medicine name = "medicine" withdrawal_stage_messages = list("", "", "") - var/datum/hallucination/fake_alert/hallucination - var/datum/hallucination/fake_health_doll/hallucination2 + /// Weakref to the "fake alert" hallucination we're giving to the addicted + var/datum/weakref/fake_alert_ref + /// Weakref to the "health doll screwup" hallucination we're giving to the addicted + var/datum/weakref/health_doll_ref /datum/addiction/medicine/withdrawal_enters_stage_1(mob/living/carbon/affected_carbon) . = ..() if(!ishuman(affected_carbon)) return - var/mob/living/carbon/human/human_mob = affected_carbon - hallucination2 = new(human_mob, TRUE, severity = 1, duration = 120 MINUTES) + var/datum/hallucination/health_doll = affected_carbon.cause_hallucination( \ + /datum/hallucination/fake_health_doll, \ + "medicine addiction", \ + severity = 1, \ + duration = 120 MINUTES, \ + ) + if(!health_doll) + return + health_doll_ref = WEAKREF(health_doll) /datum/addiction/medicine/withdrawal_stage_1_process(mob/living/carbon/affected_carbon, delta_time) . = ..() @@ -176,41 +185,64 @@ /datum/addiction/medicine/withdrawal_enters_stage_2(mob/living/carbon/affected_carbon) . = ..() var/list/possibilities = list() + if(!HAS_TRAIT(affected_carbon, TRAIT_RESISTHEAT)) - possibilities += ALERT_TEMPERATURE_HOT + possibilities += /datum/hallucination/fake_alert/hot if(!HAS_TRAIT(affected_carbon, TRAIT_RESISTCOLD)) - possibilities += ALERT_TEMPERATURE_COLD + possibilities += /datum/hallucination/fake_alert/cold + var/obj/item/organ/internal/lungs/lungs = affected_carbon.getorganslot(ORGAN_SLOT_LUNGS) if(lungs) if(lungs.safe_oxygen_min) - possibilities += ALERT_NOT_ENOUGH_OXYGEN + possibilities += /datum/hallucination/fake_alert/need_oxygen if(lungs.safe_oxygen_max) - possibilities += ALERT_TOO_MUCH_OXYGEN - var/type = pick(possibilities) - hallucination = new(affected_carbon, TRUE, type, 120 MINUTES)//last for a while basically + possibilities += /datum/hallucination/fake_alert/bad_oxygen + + if(!length(possibilities)) + return + + var/datum/hallucination/fake_alert = affected_carbon.cause_hallucination( \ + pick(possibilities), \ + "medicine addiction", \ + duration = 120 MINUTES, \ + ) + if(!fake_alert) + return + fake_alert_ref = WEAKREF(fake_alert) /datum/addiction/medicine/withdrawal_stage_2_process(mob/living/carbon/affected_carbon, delta_time) . = ..() - if(DT_PROB(10, delta_time)) - hallucination2.add_fake_limb(severity = 1) + var/datum/hallucination/fake_health_doll/hallucination = health_doll_ref?.resolve() + if(QDELETED(hallucination)) + health_doll_ref = null return + + if(DT_PROB(10, delta_time)) + hallucination.add_fake_limb(severity = 1) + return + if(DT_PROB(5, delta_time)) - hallucination2.increment_fake_damage() + hallucination.increment_fake_damage() + return /datum/addiction/medicine/withdrawal_enters_stage_3(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_CRIT + affected_carbon.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_crit, type) /datum/addiction/medicine/withdrawal_stage_3_process(mob/living/carbon/affected_carbon, delta_time) . = ..() - if(DT_PROB(5, delta_time)) - hallucination2.increment_fake_damage() + var/datum/hallucination/fake_health_doll/hallucination = health_doll_ref?.resolve() + if(!QDELETED(hallucination) && DT_PROB(5, delta_time)) + hallucination.increment_fake_damage() return + if(DT_PROB(15, delta_time)) affected_carbon.emote("cough") return + if(DT_PROB(65, delta_time)) return + if(affected_carbon.stat >= SOFT_CRIT) return @@ -218,16 +250,18 @@ if(organ.low_threshold) to_chat(affected_carbon, organ.low_threshold_passed) return + else if (organ.high_threshold_passed) to_chat(affected_carbon, organ.high_threshold_passed) return + to_chat(affected_carbon, span_warning("You feel a dull pain in your [organ.name].")) /datum/addiction/medicine/end_withdrawal(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_NONE - hallucination.cleanup() - QDEL_NULL(hallucination2) + affected_carbon.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_crit, type) + QDEL_NULL(fake_alert_ref) + QDEL_NULL(health_doll_ref) ///Nicotine /datum/addiction/nicotine diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index e1a602a2fd9..b94823cfdf8 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -194,6 +194,80 @@ if(locate(/datum/wound/burn) in wounds) . += span_warning("The flesh on this limb appears badly cooked.") +/** + * Called when a bodypart is checked for injuries. + * + * Modifies the check_list list with the resulting report of the limb's status. + */ +/obj/item/bodypart/proc/check_for_injuries(mob/living/carbon/human/examiner, list/check_list) + + var/list/limb_damage = list(BRUTE = brute_dam, BURN = burn_dam) + + SEND_SIGNAL(src, COMSIG_BODYPART_CHECKED_FOR_INJURY, examiner, check_list, limb_damage) + SEND_SIGNAL(examiner, COMSIG_CARBON_CHECKING_BODYPART, src, check_list, limb_damage) + + var/shown_brute = limb_damage[BRUTE] + var/shown_burn = limb_damage[BURN] + var/status = "" + var/self_aware = HAS_TRAIT(examiner, TRAIT_SELF_AWARE) + + if(self_aware) + if(!shown_brute && !shown_burn) + status = "no damage" + else + status = "[shown_brute] brute damage and [shown_burn] burn damage" + + else + if(shown_brute > (max_damage * 0.8)) + status += heavy_brute_msg + else if(shown_brute > (max_damage * 0.4)) + status += medium_brute_msg + else if(shown_brute > DAMAGE_PRECISION) + status += light_brute_msg + + if(shown_brute > DAMAGE_PRECISION && shown_burn > DAMAGE_PRECISION) + status += " and " + + if(shown_burn > (max_damage * 0.8)) + status += heavy_burn_msg + else if(shown_burn > (max_damage * 0.2)) + status += medium_burn_msg + else if(shown_burn > DAMAGE_PRECISION) + status += light_burn_msg + + if(status == "") + status = "OK" + + var/no_damage + if(status == "OK" || status == "no damage") + no_damage = TRUE + + var/is_disabled = "" + if(bodypart_disabled) + is_disabled = " is disabled" + if(no_damage) + is_disabled += " but otherwise" + else + is_disabled += " and" + + check_list += "\t Your [name][is_disabled][self_aware ? " has " : " is "][status]." + + for(var/datum/wound/wound as anything in wounds) + switch(wound.severity) + if(WOUND_SEVERITY_TRIVIAL) + check_list += "\t [span_danger("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)].")]" + if(WOUND_SEVERITY_MODERATE) + check_list += "\t [span_warning("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)]!")]" + if(WOUND_SEVERITY_SEVERE) + check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)]!")]" + if(WOUND_SEVERITY_CRITICAL) + check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)]!!")]" + + for(var/obj/item/embedded_thing in embedded_objects) + var/stuck_word = embedded_thing.isEmbedHarmless() ? "stuck" : "embedded" + check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!" + + /obj/item/bodypart/blob_act() receive_damage(max_damage, wound_bonus = CANT_WOUND) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 2975325fd62..fa7adac4a92 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -89,6 +89,7 @@ var/atom/drop_loc = owner.drop_location() SEND_SIGNAL(owner, COMSIG_CARBON_REMOVE_LIMB, src, dismembered) + SEND_SIGNAL(src, COMSIG_BODYPART_REMOVED, owner, dismembered) update_limb(1) owner.remove_bodypart(src) diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index ea25d7f8502..963605f82c5 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -295,7 +295,7 @@ var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) if(bz_pp > BZ_trip_balls_min) - breather.hallucination += 10 + breather.adjust_hallucinations(20 SECONDS) breather.reagents.add_reagent(/datum/reagent/bz_metabolites,5) if(bz_pp > BZ_brain_damage_min && prob(33)) breather.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index ce42e5d0144..43ef076a368 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -96,6 +96,7 @@ #include "gas_transfer.dm" #include "get_turf_pixel.dm" #include "greyscale_config.dm" +#include "hallucination_icons.dm" #include "heretic_knowledge.dm" #include "heretic_rituals.dm" #include "holidays.dm" diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm index 276048ba16c..c9a3afbfad3 100644 --- a/code/modules/unit_tests/create_and_destroy.dm +++ b/code/modules/unit_tests/create_and_destroy.dm @@ -43,8 +43,11 @@ ignore += typesof(/obj/item/poster/wanted) //This expects a seed, we can't pass it ignore += typesof(/obj/item/food/grown) - //Nothing to hallucinate if there's nothing to hallicinate - ignore += typesof(/obj/effect/hallucination) + //Needs clients / mobs to observe it to exist. Also includes hallucinations. + ignore += typesof(/obj/effect/client_image_holder) + //Same to above. Needs a client / mob / hallucination to observe it to exist. + ignore += typesof(/obj/projectile/hallucination) + ignore += typesof(/obj/item/hallucinated) //These want fried food to take on the shape of, we can't pass that in ignore += typesof(/obj/item/food/deepfryholder) //Can't pass in a thing to glow diff --git a/code/modules/unit_tests/hallucination_icons.dm b/code/modules/unit_tests/hallucination_icons.dm new file mode 100644 index 00000000000..337af74edae --- /dev/null +++ b/code/modules/unit_tests/hallucination_icons.dm @@ -0,0 +1,70 @@ +/** + * Unit tests various image related hallucinations + * that their icon_states and icons still exist, + * as often hallucinations are copy and pasted + * implementations of existing image setups + * that may be changed and not updated. + */ +/datum/unit_test/hallucination_icons + +/datum/unit_test/hallucination_icons/Run() + + // Test nearby_fake_item hallucinations for invalid image setups + for(var/datum/hallucination/nearby_fake_item/hallucination as anything in subtypesof(/datum/hallucination/nearby_fake_item)) + var/left_icon = initial(hallucination.left_hand_file) + var/right_icon = initial(hallucination.right_hand_file) + var/icon_state = initial(hallucination.image_icon_state) + check_hallucination_icon(hallucination, left_icon, icon_state) + check_hallucination_icon(hallucination, right_icon, icon_state) + + // Test preset delusion hallucinations for invalid image setups + for(var/datum/hallucination/delusion/preset/hallucination as anything in subtypesof(/datum/hallucination/delusion/preset)) + var/icon = initial(hallucination.delusion_icon_file) + var/icon_state = initial(hallucination.delusion_icon_state) + check_hallucination_icon(hallucination, icon, icon_state) + + // Test fake body hallucinations + for(var/datum/hallucination/body/husk/hallucination as anything in subtypesof(/datum/hallucination/body/husk)) + var/icon = initial(hallucination.body_image_file) + var/icon_state = initial(hallucination.body_image_state) + check_hallucination_icon(hallucination, icon, icon_state) + + // Test on_fire hallucination for if the fire icon state exists + var/datum/hallucination/fire/fire_hallucination = /datum/hallucination/fire + var/fire_hallucination_icon = initial(fire_hallucination.fire_icon) + var/fire_hallucination_icon_state = initial(fire_hallucination.fire_icon_state) + check_hallucination_icon(fire_hallucination, fire_hallucination_icon, fire_hallucination_icon_state) + + // Test shock hallucination for if the shock icon state exists + var/datum/hallucination/shock/shock_hallucination = /datum/hallucination/shock + var/shock_hallucination_icon = initial(shock_hallucination.electrocution_icon) + var/shock_hallucination_icon_state = initial(shock_hallucination.electrocution_icon_state) + check_hallucination_icon(shock_hallucination, shock_hallucination_icon, shock_hallucination_icon_state) + + // Test fake_flood hallucination for if its fake plasmaflood icon exists + var/datum/hallucination/fake_flood/flood_hallucination = /datum/hallucination/fake_flood + var/flood_hallucination_icon = initial(flood_hallucination.image_icon) + var/flood_hallucination_icon_state = initial(flood_hallucination.image_state) + check_hallucination_icon(flood_hallucination, flood_hallucination_icon, flood_hallucination_icon_state) + + // Test hallucination client_image_holders that are used for various hallucinations (bubblegum, xeno attack) + for(var/obj/effect/client_image_holder/hallucination/image_holder as anything in subtypesof(/obj/effect/client_image_holder/hallucination)) + var/icon = initial(image_holder.image_icon) + var/icon_state = initial(image_holder.image_state) + if(!icon_state || !icon) + // Not having an icon_state or icon set by default is okay, for these. + continue + + if(icon_exists(icon, icon_state)) + continue + + Fail("Hallucination image holder [image_holder] had an invalid / missing icon state for the icon [icon].") + +/datum/unit_test/hallucination_icons/proc/check_hallucination_icon(hallucination, icon, icon_state) + if(!icon) + Fail("Hallucination [hallucination] forgot to set its icon file.") + if(!icon_state) + Fail("Hallucination [hallucination] forgot to set an icon state.") + if(!icon || !icon_state || icon_exists(icon, icon_state)) + return + Fail("Hallucination [hallucination] has an invalid icon_state ([icon_state]) for its icon ([icon]).") diff --git a/strings/hallucination.json b/strings/hallucination.json index 0ef2eee8c2d..5cfdae87794 100644 --- a/strings/hallucination.json +++ b/strings/hallucination.json @@ -81,6 +81,7 @@ "help": [ "HELP HER", "HELP HIM", + "HELP MAINT", "HELP ME", "HELP THEM", "HELP US", @@ -88,8 +89,8 @@ "HELP", "HELP", "HELP", - "HELP", - "HELP" + "Help", + "Help" ], "escape": [ @@ -111,41 +112,50 @@ "people": [ "%TARGETNAME%", "AI", + "Borg", "Captain", "Ce", "Cmo", + "Geneticist", "Hop", "Hos", "Janitor", "Qm", "Rd", + "Robo", "Viro" ], "accusations": [ "a changeling", - "a clock cultist", "a cultist", "a gang leader", - "a gangster", + "a heretic", "a ling", "a rev", "a revhead", "a tator", "a traitor", - "clockcult", + "a wizard", + "bad", "cult", "dead", - "rogue" + "mindswapped", + "rogue", + "the traitor", + "the wizard" ], "threat": [ "%TARGETNAME%", "Blob", "Blue APC", + "Changeling", "Cult", "Harm", "Help", + "Heretics", + "Heretic", "I hear flashing", "Ling", "Ops", diff --git a/tgstation.dme b/tgstation.dme index 8f8e7a936a9..9470644eaa4 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -313,6 +313,7 @@ #include "code\__HELPERS\game.dm" #include "code\__HELPERS\global_lists.dm" #include "code\__HELPERS\guid.dm" +#include "code\__HELPERS\hallucinations.dm" #include "code\__HELPERS\heap.dm" #include "code\__HELPERS\hearted.dm" #include "code\__HELPERS\honkerblast.dm" @@ -1216,7 +1217,9 @@ #include "code\datums\status_effects\debuffs\drugginess.dm" #include "code\datums\status_effects\debuffs\drunk.dm" #include "code\datums\status_effects\debuffs\fire_stacks.dm" +#include "code\datums\status_effects\debuffs\hallucination.dm" #include "code\datums\status_effects\debuffs\jitteriness.dm" +#include "code\datums\status_effects\debuffs\screwy_hud.dm" #include "code\datums\status_effects\debuffs\speech_debuffs.dm" #include "code\datums\status_effects\debuffs\strandling.dm" #include "code\datums\storage\storage.dm" @@ -3051,20 +3054,27 @@ #include "code\modules\forensics\_forensics.dm" #include "code\modules\forensics\forensics_helpers.dm" #include "code\modules\hallucination\_hallucination.dm" -#include "code\modules\hallucination\airlock.dm" -#include "code\modules\hallucination\chat.dm" -#include "code\modules\hallucination\death.dm" -#include "code\modules\hallucination\fire.dm" +#include "code\modules\hallucination\battle.dm" +#include "code\modules\hallucination\body.dm" +#include "code\modules\hallucination\bolted_airlocks.dm" +#include "code\modules\hallucination\bubblegum_attack.dm" +#include "code\modules\hallucination\delusions.dm" +#include "code\modules\hallucination\fake_alert.dm" +#include "code\modules\hallucination\fake_chat.dm" +#include "code\modules\hallucination\fake_death.dm" +#include "code\modules\hallucination\fake_message.dm" +#include "code\modules\hallucination\fake_plasmaflood.dm" +#include "code\modules\hallucination\fake_sound.dm" #include "code\modules\hallucination\hazard.dm" -#include "code\modules\hallucination\hostile_mob.dm" -#include "code\modules\hallucination\HUD.dm" -#include "code\modules\hallucination\husk.dm" -#include "code\modules\hallucination\item.dm" -#include "code\modules\hallucination\plasma_flood.dm" -#include "code\modules\hallucination\polymorph.dm" +#include "code\modules\hallucination\hud_screw.dm" +#include "code\modules\hallucination\inhand_fake_item.dm" +#include "code\modules\hallucination\nearby_fake_item.dm" +#include "code\modules\hallucination\on_fire.dm" +#include "code\modules\hallucination\screwy_health_doll.dm" #include "code\modules\hallucination\shock.dm" -#include "code\modules\hallucination\sound.dm" +#include "code\modules\hallucination\station_message.dm" #include "code\modules\hallucination\stray_bullet.dm" +#include "code\modules\hallucination\xeno_attack.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" @@ -4112,7 +4122,6 @@ #include "code\modules\projectiles\projectile\special\curse.dm" #include "code\modules\projectiles\projectile\special\floral.dm" #include "code\modules\projectiles\projectile\special\gravity.dm" -#include "code\modules\projectiles\projectile\special\hallucination.dm" #include "code\modules\projectiles\projectile\special\ion.dm" #include "code\modules\projectiles\projectile\special\meteor.dm" #include "code\modules\projectiles\projectile\special\mindflayer.dm"