diff --git a/code/__defines/traits/declarations.dm b/code/__defines/traits/declarations.dm index 02abee557c..ee770890aa 100644 --- a/code/__defines/traits/declarations.dm +++ b/code/__defines/traits/declarations.dm @@ -85,6 +85,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_STASIS "in_stasis" /// Harmful radiation effects, the toxin damage and the burns, will not occur while this trait is active #define TRAIT_HALT_RADIATION_EFFECTS "halt_radiation_effects" +/// Trait applied to mobs when on Critical condition +#define TRAIT_CRITICAL_CONDITION "critical_condition" /** * diff --git a/code/__defines/traits/sources.dm b/code/__defines/traits/sources.dm index 27c2b83725..2c53463d4e 100644 --- a/code/__defines/traits/sources.dm +++ b/code/__defines/traits/sources.dm @@ -9,3 +9,5 @@ #define ACTION_TRAIT "action" /// Trait that is given by unholy effects or corruption. Can be dispelled by a chaplain with a nullrod. #define UNHOLY_TRAIT "unholy" +/// trait associated to a stat value or range of +#define STAT_TRAIT "stat" diff --git a/code/_global_vars/traits/_traits.dm b/code/_global_vars/traits/_traits.dm index 225b3de493..38ff610e63 100644 --- a/code/_global_vars/traits/_traits.dm +++ b/code/_global_vars/traits/_traits.dm @@ -37,6 +37,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_RADIMMUNE" = TRAIT_RADIMMUNE, "TRAIT_STASIS" = TRAIT_STASIS, "TRAIT_HALT_RADIATION_EFFECTS" = TRAIT_HALT_RADIATION_EFFECTS, + "TRAIT_CRITICAL_CONDITION" = TRAIT_CRITICAL_CONDITION, ), /obj = list( "TRAIT_CLIMBABLE" = TRAIT_CLIMBABLE, diff --git a/code/controllers/subsystems/research.dm b/code/controllers/subsystems/research.dm index a5516d331a..a793e257cd 100644 --- a/code/controllers/subsystems/research.dm +++ b/code/controllers/subsystems/research.dm @@ -41,10 +41,10 @@ SUBSYSTEM_DEF(research) var/list/techweb_nodes_experimental = list() ///path = list(point type = value) var/list/techweb_point_items = list( - /obj/item/research_sample/common = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS), - /obj/item/research_sample/uncommon = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS), - /obj/item/research_sample/rare = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS), - /obj/item/research_sample/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS), + /obj/item/research_sample/common = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS*0.5), + /obj/item/research_sample/uncommon = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS*0.5), + /obj/item/research_sample/rare = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS*0.5), + /obj/item/research_sample/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS*0.5), /obj/item/assembly/signaler/anomaly/bioscrambler = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), /obj/item/assembly/signaler/anomaly/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), /obj/item/assembly/signaler/anomaly/dimensional = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm index a74dbfd7d4..32ca08b9c9 100644 --- a/code/datums/components/orbiter.dm +++ b/code/datums/components/orbiter.dm @@ -146,6 +146,9 @@ return end_orbit(orbiter) +/datum/component/orbiter/proc/orbiters_length() + return LAZYLEN(orbiters) + ///////////////////// /atom/ var/datum/component/orbiter/orbiters @@ -169,3 +172,8 @@ if(!orbiters || !istype(target) || !get_turf(target) || target == src) return target.TakeComponent(orbiters) + +/atom/proc/get_orbiters_count() + if(orbiters) + return orbiters.orbiters_length() + return FALSE diff --git a/code/datums/diseases/revenant_blight.dm b/code/datums/diseases/revenant_blight.dm new file mode 100644 index 0000000000..3979c4374d --- /dev/null +++ b/code/datums/diseases/revenant_blight.dm @@ -0,0 +1,70 @@ +/datum/disease/revblight + name = "Unnatural Wasting" + medical_name = "Chameleonic Acute Depression" + desc = "A strange condition which causes the victim to feel as if they were wasting away, despite being otherwise (almost) perfectly healthy." + form = "Condition" + max_stages = 5 + stage_prob = 5 + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + cure_text = REAGENT_HOLYWATER + " or rest" + spread_text = "None" + cures = list(REAGENT_ID_HOLYWATER) + cure_chance = 30 + agent = "Unholy Forces" + viable_mobtypes = list(/mob/living/carbon/human) + disease_flags = CURABLE | CAN_NOT_POPULATE + permeability_mod = 1 + danger = DISEASE_HARMFUL + var/stagedamage = 0 + var/finalstage = 0 + var/list/original_hair_colour + +/datum/disease/revblight/cure(add_resistance = FALSE) + if(affected_mob) + affected_mob.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#1d2953") + if(original_hair_colour) + var/mob/living/carbon/human/human = affected_mob + human.change_hair_color(original_hair_colour[1], original_hair_colour[2], original_hair_colour[3]) + to_chat(affected_mob, span_notice("You feel better")) + ..() + +/datum/disease/revblight/stage_act(seconds_per_tick) + . = ..() + if(!.) + return + + if(!finalstage) + if(affected_mob.lying && SPT_PROB(3 * stage, seconds_per_tick)) + cure() + return FALSE + if(SPT_PROB(1.5 * stage, seconds_per_tick)) + to_chat(affected_mob, span_danger("You suddenly feel [pick("sick and tired", "disoriented", "tired and confused", "nauseated", "faint", "dizzy")]...")) + affected_mob.Confuse(10) + new /obj/effect/temp_visual/revenant(affected_mob.loc) + if(stagedamage < stage) + stagedamage++ + affected_mob.adjustToxLoss(1 * stage * seconds_per_tick) + new /obj/effect/temp_visual/revenant(affected_mob.loc) + + switch(stage) + if(2) + if(prob(5)) + affected_mob.emote("pale") + if(3) + if(prob(10)) + affected_mob.emote(pick("pale", "shiver")) + if(4) + if(prob(15)) + affected_mob.emote(pick("pale", "shiver", "cry")) + if(5) + if(!finalstage) + finalstage = TRUE + to_chat(affected_mob, span_danger("You feel like [pick("nothing's worth it anymore", "nobody ever needed your help", "nothing you did mattered", "everything you did was worthless.")].")) + new /obj/effect/temp_visual/revenant(affected_mob.loc) + if(ishuman(affected_mob)) + var/mob/living/carbon/human/human = affected_mob + original_hair_colour = list(human.r_hair, human.g_hair, human.b_hair) + human.change_hair_color(255, 255, 255) + affected_mob.visible_message(span_warning("[affected_mob] looks terrifyingly gaunt..."), span_danger("You suddenly feel like your skin is wrong...")) + affected_mob.add_atom_colour("#1d2953", TEMPORARY_COLOUR_PRIORITY) + addtimer(CALLBACK(src, PROC_REF(cure)), 10 SECONDS) diff --git a/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm b/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm new file mode 100644 index 0000000000..92ce961b6a --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm @@ -0,0 +1,93 @@ +/obj/effect/anomaly/ectoplasm + name = "ectoplasm anomaly" + desc = "It looks like the souls of the damned are trying to break into the realm of the living again. How upsetting." + icon_state = "ectoplasm" + anomaly_core = /obj/item/assembly/signaler/anomaly/ectoplasm + lifespan = ANOMALY_COUNTDOWN_TIMER + 2 SECONDS + + ///Blocks the anomaly from updating ghost count. Used in case an admin wants to rig the anomaly to be a certain size or intensity. + var/override_ghosts = FALSE + ///The numerical power of the anomaly. Calculated in anomalyEffect. Also used in determining the category of detonation effects. + var/effect_power = 0 + ///The actual number of ghosts orbiting the anomaly. + var/ghosts_orbiting = 0 + +/obj/effect/anomaly/ectoplasm/examine(mob/user, infix, suffix) + . = ..() + + if(isobserver(user)) + . += span_info("Orbiting this anomaly will increase the size and intensity of its effects.") + + switch(effect_power) + if(0 to 25) + . += span_notice("The space around the anomaly faintly resonates. It doesn't seem very powerful at the moment.") + if(26 to 49) + . += span_notice("The space around the anomaly seems to vibrate, letting out a noise that sounds like ghastly moaning. Someone should probably do something about that.") + if(50 to 100) + . += span_alert("The anomaly pulsates heavily, about to burst with unearthly energy. This can't be good.") + +/obj/effect/anomaly/ectoplasm/anomalyEffect(seconds_per_tick) + . = ..() + + if(override_ghosts) + return + + ghosts_orbiting = get_orbiters_count() + + if(ghosts_orbiting) + var/total_death = 0 + for(var/mob/mob in GLOB.dead_mob_list) + if(mob.client) + total_death++ + effect_power = ghosts_orbiting / total_death * 100 + else + effect_power = 0 + + intensity_update() + +/obj/effect/anomaly/ectoplasm/detonate() + . = ..() + + if(effect_power < 10) + new /obj/effect/temp_visual/revenant/cracks(get_turf(src)) + return + + if(effect_power >= 10) + var/effect_range = ghosts_orbiting + 3 + for(var/impacted_thing in range(effect_range, get_turf(src))) + if(isfloorturf(impacted_thing)) + if(prob(5)) + new /obj/effect/decal/cleanable/blood(get_turf(impacted_thing)) + else if(prob(10)) + new /obj/effect/decal/cleanable/greenglow(get_turf(impacted_thing)) + else if(prob(10)) + new /obj/effect/decal/cleanable/dirt(get_turf(impacted_thing)) + + if(!istype(impacted_thing, /turf/simulated/floor/plating)) + var/turf/simulated/floor/floor_to_break = impacted_thing + floor_to_break.break_tile() + + if(ishuman(impacted_thing)) + var/mob/living/carbon/human/mob_to_infect = impacted_thing + mob_to_infect.ForceContractDisease(new /datum/disease/revblight, FALSE) + new /obj/effect/temp_visual/revenant(get_turf(mob_to_infect)) + to_chat(mob_to_infect, span_danger("A cacophony of ghostly wailing floods your ears for a moment. The noise subsides, but a distant whispering continues echoing inside of your head...")) + + if(istype(impacted_thing, /obj/structure/window)) + var/obj/structure/window/window_to_damage = impacted_thing + window_to_damage.take_damage(rand(60, 90)) + if(window_to_damage?.fulltile) + new /obj/effect/temp_visual/revenant/cracks(get_turf(window_to_damage)) + /* + if(effect_power >= 35) + + + if(effect_power >= 50) + */ + +/obj/effect/anomaly/ectoplasm/proc/intensity_update() + if(effect_power >= 50) + icon_state = "ectoplasm_heavy" + else + icon_state = "ectoplasm" + update_icon() diff --git a/code/game/objects/effects/anomalies/anomaly_stats.dm b/code/game/objects/effects/anomalies/anomaly_stats.dm index 47e2cf1480..bd6f71c621 100644 --- a/code/game/objects/effects/anomalies/anomaly_stats.dm +++ b/code/game/objects/effects/anomalies/anomaly_stats.dm @@ -44,7 +44,7 @@ transformation_type = particles[4] /datum/anomaly_stats/proc/calculate_points() - var/total = 10 + var/total = 15 var/obj/effect/anomaly/anomaly = attached_anomaly.resolve() @@ -72,7 +72,7 @@ return else if(particle == containment_type) update_state(FALSE) - update_health(-1, -10) + update_health(-1, -5) return else if(particle == transformation_type) update_modifiers() diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 961a4dd5a3..ee0c2bce53 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -158,3 +158,12 @@ icon_state = "emppulse" duration = 8 randomdir = 0 + +/obj/effect/temp_visual/revenant + name = "spooky lights" + icon_state = "purplesparkles" + +/obj/effect/temp_visual/revenant/cracks + name = "glowing cracks" + icon_state = "purplecrack" + duration = 6 diff --git a/code/modules/anomalies/anomaly.dm b/code/modules/anomalies/anomaly.dm index 76ba207049..fd51e582b3 100644 --- a/code/modules/anomalies/anomaly.dm +++ b/code/modules/anomalies/anomaly.dm @@ -185,6 +185,8 @@ icon = 'icons/obj/assemblies/new_assemblies.dmi' icon_state = "inert" worth = 0 + var/options = 3 + var/list/choices var/picked = FALSE anomaly_type = /obj/effect/anomaly/flux // Default @@ -196,22 +198,22 @@ if(picked) return TRUE - var/list/choices = list() - var/list/core_types = subtypesof(/obj/effect/anomaly) + if(isnull(choices)) + choices = list() + var/list/core_types = subtypesof(/obj/effect/anomaly) - // Two random cores - for(var/i = 0, i < 2, i++) - var/type = pick(core_types) - var/obj/effect/anomaly/anom = new type - choices[capitalize(anom.name)] = type - - // Guaranteed - var/preset = /obj/effect/anomaly/flux - var/obj/effect/anomaly/preset_anom = new preset - choices[capitalize(preset_anom.name)] = preset + for(var/i = 0, i < options, i++) + var/type = pick_n_take(core_types) + var/obj/effect/anomaly/anom = new type + choices[capitalize(anom.name)] = type var/choice = tgui_input_list(user, "Choose an anomaly core.", "Anomaly Core Selection", choices) if(choice && !picked) anomaly_type = choices[choice] picked = TRUE + +/obj/item/assembly/signaler/anomaly/choice/Destroy() + for(var/obj/effect/anomaly/anom in options) + qdel(anom) + . = ..() diff --git a/code/modules/clothing/accessories/accessory.dm b/code/modules/clothing/accessories/accessory.dm index 5ce8749228..84b51d8240 100644 --- a/code/modules/clothing/accessories/accessory.dm +++ b/code/modules/clothing/accessories/accessory.dm @@ -270,6 +270,11 @@ sound = "a light, rhythmic, mechanical clicking" else sound = span_warning("no heartbeat") + if(istype(heart, /obj/item/organ/internal/heart/machine/anomalock)) + var/obj/item/organ/internal/heart/machine/anomalock/zap_heart = heart + if(zap_heart.core) + user.electrocute_act(15, src) + user.emote("scream") else switch(M.pulse) if(PULSE_NONE) diff --git a/code/modules/events/anomalies.dm b/code/modules/events/anomalies.dm new file mode 100644 index 0000000000..a841c4f568 --- /dev/null +++ b/code/modules/events/anomalies.dm @@ -0,0 +1,134 @@ +/datum/event/anomaly + startWhen = 15 + announceWhen = 1 + var/area/impact_area + var/datum/anomaly_placer/placer = new() + var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux + +/datum/event/anomaly/setup() + impact_area = placer.find_valid_area() + +/datum/event/anomaly/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Energetic flux wave detected on [ANOMALY_ANNOUNCE_DANGEROUS_TEXT] [impact_area.name].", "Anomaly Alert") + +/datum/event/anomaly/start() + var/turf/anomaly_turf + anomaly_turf = placer.find_valid_turf(impact_area) + + var/newAnomaly + if(anomaly_turf) + newAnomaly = new anomaly_path(anomaly_turf) + if(newAnomaly) + apply_anomaly_properties(newAnomaly) + +/datum/event/anomaly/proc/apply_anomaly_properties(obj/effect/anomaly/new_anomaly) + return + +// Bioscrambler +/datum/event/anomaly/bioscrambler + startWhen = 3 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/bioscrambler + +/datum/event/anomaly/bioscrambler/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Biologic trait swapping agent detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name].", "Anomaly Alert") + +// Bluespace +/datum/event/anomaly/bluespace + startWhen = 3 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/bluespace + +/datum/event/anomaly/bluespace/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Bluespace instability detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name].", "Anomaly Alert") + +// Dimensional +/datum/event/anomaly/dimensional + startWhen = 3 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/dimensional + var/anomaly_theme + +/datum/event/anomaly/dimensional/apply_anomaly_properties(obj/effect/anomaly/new_anomaly) + if(!anomaly_theme) + return + var/obj/effect/anomaly/dimensional/anomaly = new_anomaly + anomaly.prepare_area(new_theme_path = anomaly_theme) + +/datum/event/anomaly/dimensional/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Dimensional instability detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name].", "Anomaly Alert") + +// Flux + +/datum/event/anomaly/flux + startWhen = 15 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/flux + +/datum/event/anomaly/flux/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Hyper-energetic flux wave detected on [ANOMALY_ANNOUNCE_DANGEROUS_TEXT] [impact_area.name].", "Anomaly Alert") + +// Gravitational +/datum/event/anomaly/grav + startWhen = 3 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/grav + +/datum/event/anomaly/grav/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Gravitational anomaly detected on [ANOMALY_ANNOUNCE_HARMFUL_TEXT] [impact_area.name].", "Anomaly Alert") + +// Hallucination +/datum/event/anomaly/hallucination + startWhen = 3 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/hallucination + +/datum/event/anomaly/hallucination/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Hallucinatory event detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name].", "Anomaly Alert") + +// Pyroclastic +/datum/event/anomaly/pyro + startWhen = 15 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/pyro + +/datum/event/anomaly/pyro/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Pyroclastic anomaly detected on [ANOMALY_ANNOUNCE_HARMFUL_TEXT] [impact_area.name].", "Anomaly Alert") + +// Weather +/datum/event/anomaly/weather + startWhen = 3 + announceWhen = 1 + anomaly_path = /obj/effect/anomaly/weather + +/datum/event/anomaly/weather/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Metereologic anomaly detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name].", "Anomaly Alert") + +// Dust +/datum/event/anomaly/dust + startWhen = 3 + announceWhen = 15 + anomaly_path = /obj/effect/anomaly/dust + +/datum/event/anomaly/dust/announce() + if(isnull(impact_area)) + impact_area = placer.find_valid_area() + command_announcement.Announce("Anomalous dust particles detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index ce420b88fb..b273e43bdc 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1262,6 +1262,8 @@ set_stat(UNCONSCIOUS) blinded = TRUE in_crit = TRUE + if(!HAS_TRAIT(src, TRAIT_CRITICAL_CONDITION)) + ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) if(hallucination) if(hallucination >= HALLUCINATION_THRESHOLD && !(species.flags & (NO_POISON|IS_PLANT|NO_HALLUCINATION)) && !HAS_TRAIT(src, TRAIT_MADNESS_IMMUNE)) @@ -1343,6 +1345,8 @@ else if(!in_crit) set_stat(CONSCIOUS) clear_alert("asleep") + if(HAS_TRAIT(src, TRAIT_CRITICAL_CONDITION)) + REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) //Periodically double-check embedded_flag if(embedded_flag && !(life_tick % 10)) diff --git a/code/modules/organs/internal/special/heart_anomalock.dm b/code/modules/organs/internal/special/heart_anomalock.dm new file mode 100644 index 0000000000..105f670fcd --- /dev/null +++ b/code/modules/organs/internal/special/heart_anomalock.dm @@ -0,0 +1,155 @@ +/obj/item/organ/internal/heart/machine/anomalock + name = "voltaic combat cyberheart" + desc = "A cutting-edge cyberheart. Voltaic technology allows the heart to keep the body upright in dire circumstances, alongside redirecting anomalous flux energy to fully shield the user from shocks and electro-magnetic pulses. Requires a Flux core as a power source." + icon_state = "anomalock_heart" + organ_tag = O_HEART + + COOLDOWN_DECLARE(survival_cooldown) + ///Cooldown for the activation of the organ + var/survival_cooldown_time = 10 MINUTES + ///The lightning effect on our mob when the implant is active + var/mutable_appearance/lightning_overlay + ///how long the lightning lasts + var/lightning_timer + + ///The core item the organ runs off. + var/obj/item/assembly/signaler/anomaly/core + ///Accepted types of anomaly cores. + var/required_anomaly = /obj/item/assembly/signaler/anomaly/flux + ///If this one starts with a core in. + var/prebuilt = FALSE + ///If the core is removable once socketed. + var/core_removable = TRUE + +/obj/item/organ/internal/heart/machine/anomalock/Destroy() + if(lightning_timer) + deltimer(lightning_timer) + if(lightning_overlay) + lightning_overlay = null + QDEL_NULL(core) + return ..() + +/obj/item/organ/internal/heart/machine/anomalock/handle_organ_mod_special(removed) + if(!core) + return + + if(!removed) + add_lightning_overlay(30 SECONDS) + playsound(owner, 'sound/machines/defib_zap.ogg', 50, TRUE, -1) + owner.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS) + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_CRITICAL_CONDITION), PROC_REF(activate_survival)) + RegisterSignal(owner, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act)) + + if(removed) + clear_lightning_overlay(owner) + UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_CRITICAL_CONDITION)) + UnregisterSignal(owner, COMSIG_ATOM_EMP_ACT) + owner.RemoveElement(/datum/element/empprotection) + tesla_zap(owner, 10, 2500, current_jumps = 5) + QDEL_IN(src, 0) + + ..(removed) + +/obj/item/organ/internal/heart/machine/anomalock/proc/add_lightning_overlay(time_to_last = 10 SECONDS) + if(lightning_overlay) + lightning_overlay = addtimer(CALLBACK(src, PROC_REF(clear_lightning_overlay), owner), time_to_last, (TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE|TIMER_DELETE_ME)) + return + lightning_overlay = mutable_appearance(icon = 'icons/effects/effects.dmi', icon_state = "lightning") + owner.add_overlay(lightning_overlay) + lightning_timer = addtimer(CALLBACK(src, PROC_REF(clear_lightning_overlay), owner), time_to_last, (TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE|TIMER_DELETE_ME)) + +/obj/item/organ/internal/heart/machine/anomalock/proc/clear_lightning_overlay(mob/organ_owner) + organ_owner?.cut_overlay(lightning_overlay) + if(lightning_timer) + deltimer(lightning_timer) + lightning_overlay = null + +/obj/item/organ/internal/heart/machine/anomalock/proc/activate_survival(mob/living/carbon/organ_owner) + if(!COOLDOWN_FINISHED(src, survival_cooldown)) + return FALSE + + organ_owner.add_modifier(/datum/modifier/voltaic_overdrive, 30 SECONDS) + add_lightning_overlay(30 SECONDS) + COOLDOWN_START(src, survival_cooldown, survival_cooldown_time) + addtimer(CALLBACK(src, PROC_REF(notify_cooldown), organ_owner), COOLDOWN_TIMELEFT(src, survival_cooldown)) + return TRUE + +/obj/item/organ/internal/heart/machine/anomalock/proc/notify_cooldown(mob/living/carbon/organ_owner) + balloon_alert(organ_owner, "your heart strengthtens") + playsound(owner, 'sound/machines/defib_zap.ogg', 40) + +/obj/item/organ/internal/heart/machine/anomalock/proc/on_emp_act(severity, recursive) + SIGNAL_HANDLER + add_lightning_overlay(10 SECONDS) + +/obj/item/organ/internal/heart/machine/anomalock/attackby(obj/item/W, mob/user) + if(istype(W, required_anomaly)) + if(core) + balloon_alert(user, "core already in!") + return FALSE + if(do_after(user, 3 SECONDS, src)) + user.unEquip(W, TRUE, src) + core = W + balloon_alert(user, "core_installed") + playsound(src, 'sound/machines/click.ogg') + update_icon() + return TRUE + + if(W.has_tool_quality(IS_SCREWDRIVER)) + if(!core) + balloon_alert(user, "no core!") + return FALSE + if(!core_removable) + balloon_alert(user, "can't remove core!") + return FALSE + balloon_alert(user, "removing core...") + if(!do_after(user, 3 SECONDS, src)) + balloon_alert(user, "interrupted!") + return + balloon_alert(user, "core removed") + core.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(core) + core = null + update_icon() + return TRUE + + return ..() + +/obj/item/organ/internal/heart/machine/anomalock/prebuilt/Initialize(mapload, internal) + . = ..() + core = new /obj/item/assembly/signaler/anomaly/flux(src) + update_icon() + +/obj/item/organ/internal/heart/machine/anomalock/update_icon() + . = ..() + icon_state = initial(icon_state) + (core ? "-core" : "") + +/datum/modifier/voltaic_overdrive + name = "voltaic_overdrive" + client_color = "#e9f76b" + +/datum/modifier/voltaic_overdrive/tick(seconds_between_ticks) + . = ..() + if(holder.health > holder.get_crit_point()) + return + + holder.heal_overall_damage(5, 5) + holder.adjustOxyLoss(-5) + holder.adjustToxLoss(-5) + holder.adjustHalLoss(-5) + holder.AdjustWeakened(-5) + holder.AdjustSleeping(-5) + holder.AdjustStunned(-5) + holder.eye_blurry = 0 + +/datum/modifier/voltaic_overdrive/on_applied() + . = ..() + REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + holder.reagents.add_reagent(REAGENT_ID_MYELAMINE, 5) + to_chat(holder, span_userdanger("You feel a burst of energy! It's do or die!")) + pain_immunity = TRUE + +/datum/modifier/voltaic_overdrive/on_expire() + . = ..() + holder.balloon_alert(holder, "your heart weakens") diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index ae0b693a54..3e694ef745 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -49,6 +49,8 @@ if(!core.anomaly_type) return FALSE + user.unEquip(core, TRUE, drop_location()) + var/obj/effect/anomaly/anomaly = new core.anomaly_type(get_turf(core)) anomaly.stabilize(releaser.will_anchor, releaser.has_core, releaser.gives_stats) @@ -234,3 +236,11 @@ /obj/item/assembly/signaler/anomaly/dust/proc/extraCough(mob/living/coughing) coughing.emote("cough") addtimer(CALLBACK(coughing, TYPE_PROC_REF(/mob, emote), "cough"), 3 SECONDS) +/* +/obj/item/assembly/signaler/anomaly/ectoplasm + name = "\improper ectoplasm anomaly core" + desc = "The neutralized core of an ectoplasmic anomaly. When you hold it close, you can hear faint murmuring from inside. It'd probably be valuable for research." + icon_state = "dimensional_core" + anomaly_type = /obj/effect/anomaly/ectoplasm + activation_cooldown = 60 SECONDS +*/ diff --git a/code/modules/research/tg/designs/medical_designs.dm b/code/modules/research/tg/designs/medical_designs.dm index bfb7db8ac9..4b7427a8f1 100644 --- a/code/modules/research/tg/designs/medical_designs.dm +++ b/code/modules/research/tg/designs/medical_designs.dm @@ -432,3 +432,20 @@ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MEDICAL ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_MEDICAL + +/datum/design_techweb/anomalock + name = "Voltaic combat cyberheart" + desc = "A cutting-edge cyberheart. Voltaic technology allows the heart to keep the body upright in dire circumstances, alongside redirecting anomalous flux energy to fully shield the user from shocks and electro-magnetic pulses. Does nothing without a flux anomaly core." + id = "cybernetic_heart_anomalock" + construction_time = 5 SECONDS + materials = list( + MAT_STEEL = 2500, + MAT_GLASS = 2500, + MAT_TITANIUM = 10000, + MAT_DIAMOND = 5000 + ) + build_path = /obj/item/organ/internal/heart/machine/anomalock + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SCIENCE + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE diff --git a/code/modules/research/tg/techwebs/nodes/research_nodes.dm b/code/modules/research/tg/techwebs/nodes/research_nodes.dm index 8bd58dcb0d..3459142d34 100644 --- a/code/modules/research/tg/techwebs/nodes/research_nodes.dm +++ b/code/modules/research/tg/techwebs/nodes/research_nodes.dm @@ -140,6 +140,17 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS) announce_channels = list(CHANNEL_SCIENCE) +/datum/techweb_node/anomaly_shells + id = TECHWEB_NODE_ANOMALY_SHELLS + display_name = "Advanced Anomaly Shells" + description = "New shells designed to utilize anomaly cores, maximizing their potential in innovative ways." + prereq_ids = list(TECHWEB_NODE_ANOMALY_RESEARCH) + design_ids = list( + "cybernetic_heart_anomalock" + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) + announce_channels = list(CHANNEL_SCIENCE) + /datum/techweb_node/anomaly_harvesting id = TECHWEB_NODE_ANOMALY_HARVESTING display_name = "Anomaly Harvesting" diff --git a/code/modules/samples/samples.dm b/code/modules/samples/samples.dm index 04d9d96073..a78b25230f 100644 --- a/code/modules/samples/samples.dm +++ b/code/modules/samples/samples.dm @@ -27,7 +27,7 @@ if(!fixed_name) var/name_prefix = "[pick("strange","anomalous","exotic","atypical","unusual","incongruous","weird","aberrant","eccentric")]" var/name_suffix //blank because it's randomized per sample appearance - var/sample_icon = rand(1,10) + var/sample_icon = rand(1,11) icon_state = "generic_sample[sample_icon]" damage_type = pick("BRUTE","BURN","TOX","OXY","EMP","PAIN") //per-state tweaks, like glows/light emission or narrower valid tech defs, if desired diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index f60a4c7c08..2833117382 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/obj/samples.dmi b/icons/obj/samples.dmi index 7fd6fa3bc6..d14c7ddab7 100644 Binary files a/icons/obj/samples.dmi and b/icons/obj/samples.dmi differ diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi index 964e0202a0..b3f121acd0 100644 Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ diff --git a/vorestation.dme b/vorestation.dme index 8c7ff986d9..0706c7df8e 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -846,6 +846,7 @@ #include "code\datums\diseases\horror.dm" #include "code\datums\diseases\lycancoughy.dm" #include "code\datums\diseases\magnitis.dm" +#include "code\datums\diseases\revenant_blight.dm" #include "code\datums\diseases\roanoke.dm" #include "code\datums\diseases\advance\advance.dm" #include "code\datums\diseases\advance\disease_preset.dm" @@ -4270,6 +4271,7 @@ #include "code\modules\organs\internal\robotic\diagnostic.dm" #include "code\modules\organs\internal\robotic\heatsink.dm" #include "code\modules\organs\internal\robotic\robotic.dm" +#include "code\modules\organs\internal\special\heart_anomalock.dm" #include "code\modules\organs\subtypes\diona.dm" #include "code\modules\organs\subtypes\indestructible.dm" #include "code\modules\organs\subtypes\lleill.dm"