diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 3d08f8d7099..1d92e8ac265 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -267,6 +267,7 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list( GLOBAL_LIST_INIT(book_types, typecacheof(list( /obj/item/book, /obj/item/spellbook, + /obj/item/infuser_book, ))) // Jobs diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index f48d46dbb3c..6414638e5d0 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -567,10 +567,14 @@ ///Squash flags. For squashable element -///Whether or not the squashing requires the squashed mob to be lying down +/// Squashing will not occur if the mob is not lying down (bodyposition is LYING_DOWN) #define SQUASHED_SHOULD_BE_DOWN (1<<0) -///Whether or not to gib when the squashed mob is moved over -#define SQUASHED_SHOULD_BE_GIBBED (1<<0) +/// If present, outright gibs the squashed mob instead of just dealing damage +#define SQUASHED_SHOULD_BE_GIBBED (1<<1) +/// If squashing always passes if the mob is dead +#define SQUASHED_ALWAYS_IF_DEAD (1<<2) +/// Don't squash our mob if its not located in a turf +#define SQUASHED_DONT_SQUASH_IN_CONTENTS (1<<3) /* * Defines for "AI emotions", allowing the AI to expression emotions diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index eca2380e121..77640b4e741 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -214,9 +214,15 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_RESISTLOWPRESSURE "resist_low_pressure" /// This human is immune to the effects of being exploded. (ex_act) #define TRAIT_BOMBIMMUNE "bomb_immunity" +/// Immune to being irradiated #define TRAIT_RADIMMUNE "rad_immunity" -#define TRAIT_GENELESS "geneless" +/// This mob won't get gibbed by nukes going off +#define TRAIT_NUKEIMMUNE "nuke_immunity" +/// Can't be given viruses #define TRAIT_VIRUSIMMUNE "virus_immunity" +/// Reduces the chance viruses will spread to this mob, and if the mob has a virus, slows its advancement +#define TRAIT_VIRUS_RESISTANCE "virus_resistance" +#define TRAIT_GENELESS "geneless" #define TRAIT_PIERCEIMMUNE "pierce_immunity" #define TRAIT_NODISMEMBER "dismember_immunity" #define TRAIT_NOFIRE "nonflammable" diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm index 6b7e5543d57..9fe3db3ada1 100644 --- a/code/__HELPERS/matrices.dm +++ b/code/__HELPERS/matrices.dm @@ -68,6 +68,20 @@ //doesn't have an object argument because this is "Stacking" with the animate call above //3 billion% intentional +/// Similar to shake but more spasm-y and jerk-y +/atom/proc/spasm_animation(loops = -1) + var/list/transforms = list( + matrix(transform).Translate(-1, 0), + matrix(transform).Translate(0, 1), + matrix(transform).Translate(1, 0), + matrix(transform).Translate(0, -1), + ) + + animate(src, transform = transforms[1], time = 0.2, loop = loops) + animate(transform = transforms[2], time = 0.1) + animate(transform = transforms[3], time = 0.2) + animate(transform = transforms[4], time = 0.3) + /** * Shear the transform on either or both axes. * * x - X axis shearing diff --git a/code/datums/components/squashable.dm b/code/datums/components/squashable.dm index a8304320bdb..a79a1802f04 100644 --- a/code/datums/components/squashable.dm +++ b/code/datums/components/squashable.dm @@ -37,11 +37,13 @@ return var/mob/living/parent_as_living = parent - - if(squash_flags & SQUASHED_SHOULD_BE_DOWN && parent_as_living.body_position != LYING_DOWN) + if((squash_flags & SQUASHED_DONT_SQUASH_IN_CONTENTS) && !isturf(parent_as_living.loc)) return - var/should_squash = prob(squash_chance) + if((squash_flags & SQUASHED_SHOULD_BE_DOWN) && parent_as_living.body_position != LYING_DOWN) + return + + var/should_squash = ((squash_flags & SQUASHED_ALWAYS_IF_DEAD) && parent_as_living.stat == DEAD) || prob(squash_chance) if(should_squash && on_squash_callback) if(on_squash_callback.Invoke(parent_as_living, crossing_movable)) diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm index 734f67eb0ce..ce0e6169a73 100644 --- a/code/datums/diseases/_MobProcs.dm +++ b/code/datums/diseases/_MobProcs.dm @@ -64,7 +64,7 @@ if(ishuman(src)) var/mob/living/carbon/human/infecting_human = src - if(infecting_human.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) && prob(75)) + if(HAS_TRAIT(infecting_human, TRAIT_VIRUS_RESISTANCE) && prob(75)) return switch(target_zone) @@ -95,10 +95,8 @@ disease.try_infect(src) /mob/living/proc/AirborneContractDisease(datum/disease/disease, force_spread) - if(ishuman(src)) - var/mob/living/carbon/human/infecting_human = src - if(infecting_human.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) && prob(75)) - return + if(HAS_TRAIT(src, TRAIT_VIRUS_RESISTANCE) && prob(75)) + return if(((disease.spread_flags & DISEASE_SPREAD_AIRBORNE) || force_spread) && prob((50*disease.spreading_modifier) - 1)) ForceContractDisease(disease) diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm index 4f5b3b950c9..88e642c2e96 100644 --- a/code/datums/diseases/_disease.dm +++ b/code/datums/diseases/_disease.dm @@ -64,7 +64,7 @@ ///Proc to process the disease and decide on whether to advance, cure or make the sympthoms appear. Returns a boolean on whether to continue acting on the symptoms or not. /datum/disease/proc/stage_act(seconds_per_tick, times_fired) - var/slowdown = affected_mob.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) ? 0.5 : 1 // spaceacillin slows stage speed by 50% + var/slowdown = HAS_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE) ? 0.5 : 1 // spaceacillin slows stage speed by 50% if(has_cure()) if(disease_flags & CHRONIC && SPT_PROB(cure_chance, seconds_per_tick)) @@ -104,7 +104,7 @@ if(!(spread_flags & DISEASE_SPREAD_AIRBORNE) && !force_spread) return - if(affected_mob.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) || (affected_mob.satiety > 0 && prob(affected_mob.satiety/10))) + if(HAS_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE) || (affected_mob.satiety > 0 && prob(affected_mob.satiety/10))) return var/spread_range = 2 diff --git a/code/datums/elements/bugkiller_reagent.dm b/code/datums/elements/bugkiller_reagent.dm new file mode 100644 index 00000000000..57f2ae65d92 --- /dev/null +++ b/code/datums/elements/bugkiller_reagent.dm @@ -0,0 +1,88 @@ +/// Simple element to be applied to reagents +/// When those reagents are exposed to mobs with the bug biotype, causes toxins damage +/// If this delivers the killing blow on a non-humanoid mob, it applies a special status effect that does a funny animation +/datum/element/bugkiller_reagent + +/datum/element/bugkiller_reagent/Attach(datum/target) + . = ..() + if(!istype(target, /datum/reagent)) + return + + RegisterSignal(target, COMSIG_REAGENT_EXPOSE_MOB, PROC_REF(on_expose)) + +/datum/element/bugkiller_reagent/Detach(datum/source, ...) + . = ..() + UnregisterSignal(source, COMSIG_REAGENT_EXPOSE_MOB) + +/datum/element/bugkiller_reagent/proc/on_expose( + datum/reagent/source, + mob/living/exposed_mob, + methods = TOUCH, + reac_volume, + show_message = TRUE, + touch_protection = 0, +) + SIGNAL_HANDLER + + if(exposed_mob.stat == DEAD) + return + if(!(exposed_mob.mob_biotypes & MOB_BUG)) + return + + // capping damage so splashing a beaker on a moth is not an instant crit + var/damage = min(round(0.4 * reac_volume * (1 - touch_protection), 0.1), 12) + if(damage < 1) + return + + if(!(exposed_mob.mob_biotypes & MOB_HUMANOID) && exposed_mob.health <= damage) + // no-ops if they are already in the process of dying + exposed_mob.apply_status_effect(/datum/status_effect/bugkiller_death) + return + + if(exposed_mob.apply_damage(damage, TOX) && damage >= 6) + // yes i know it's not burn damage. the burning is on the inside. + to_chat(exposed_mob, span_danger("You feel a burning sensation.")) + +/// If bugkiller delivers a lethal dosage, applies this effect which does a funny animation THEN kills 'em +/// Also makes it so simplemobs / basicmobs no longer delete when they die (if they do) +/datum/status_effect/bugkiller_death + id = "bugkiller_death" + alert_type = /atom/movable/screen/alert/status_effect/bugkiller_death + /// How many times the spasm loops + var/spasm_loops = 0 + +/datum/status_effect/bugkiller_death/on_creation(mob/living/new_other, duration = 4 SECONDS) + src.duration = duration + src.spasm_loops = ROUND_UP(duration / 0.8) // one spasm ~= 0.8 deciseconds (yes deciseconds) + return ..() + +/datum/status_effect/bugkiller_death/on_apply() + if(owner.stat == DEAD) + return FALSE + playsound(owner, 'sound/voice/human/malescream_1.ogg', 25, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, frequency = 5) + to_chat(owner, span_userdanger("The world begins to go dark...")) + owner.spasm_animation(spasm_loops) + owner.adjust_eye_blur(duration) + return TRUE + +/datum/status_effect/bugkiller_death/on_remove() + if(owner.stat == DEAD || QDELETED(owner)) + return + + if(isbasicmob(owner)) + var/mob/living/basic/basic_owner = owner + basic_owner.basic_mob_flags &= ~DEL_ON_DEATH + basic_owner.basic_mob_flags |= FLIP_ON_DEATH + + if(isanimal(owner)) + var/mob/living/simple_animal/simple_owner = owner + simple_owner.del_on_death = FALSE + simple_owner.flip_on_death = TRUE + + owner.investigate_log("died to being sprayed with bugkiller.", INVESTIGATE_DEATHS) + owner.death() + +/atom/movable/screen/alert/status_effect/bugkiller_death + name = "Overwhelming Toxicity" + desc = "Don't go into the light!" + icon_state = "paralysis" diff --git a/code/datums/greyscale/json_configs/mutant_organs.json b/code/datums/greyscale/json_configs/mutant_organs.json index 93dd66c9e64..2e4aa1da884 100644 --- a/code/datums/greyscale/json_configs/mutant_organs.json +++ b/code/datums/greyscale/json_configs/mutant_organs.json @@ -1,4 +1,18 @@ { + "appendix": [ + { + "type": "icon_state", + "icon_state": "appendix", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "appendix_insides", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ], "brain": [ { "type": "icon_state", diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index 21b5e0040e8..e305727914a 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -41,7 +41,7 @@ return if(victim.reagents) - if(victim.reagents.has_reagent(/datum/reagent/medicine/spaceacillin)) + if(HAS_TRAIT(victim, TRAIT_VIRUS_RESISTANCE)) sanitization += 0.9 if(victim.reagents.has_reagent(/datum/reagent/space_cleaner/sterilizine/)) sanitization += 0.9 diff --git a/code/game/machinery/dna_infuser/dna_infuser.dm b/code/game/machinery/dna_infuser/dna_infuser.dm index ab8ee85f52f..8275eb1e948 100644 --- a/code/game/machinery/dna_infuser/dna_infuser.dm +++ b/code/game/machinery/dna_infuser/dna_infuser.dm @@ -136,6 +136,7 @@ new_organ = new new_organ() new_organ.replace_into(target) check_tier_progression(target) + return TRUE /// Picks a random mutated organ from the infuser entry which is also compatible with the target mob. /// Tries to return a typepath of a valid mutant organ if all of the following criteria are true: diff --git a/code/game/machinery/dna_infuser/infuser_book.dm b/code/game/machinery/dna_infuser/infuser_book.dm index 84a78a4899a..ea5dbcbf411 100644 --- a/code/game/machinery/dna_infuser/infuser_book.dm +++ b/code/game/machinery/dna_infuser/infuser_book.dm @@ -8,12 +8,22 @@ throw_speed = 2 throw_range = 5 w_class = WEIGHT_CLASS_TINY + drop_sound = 'sound/items/handling/book_drop.ogg' + pickup_sound = 'sound/items/handling/book_pickup.ogg' /obj/item/infuser_book/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "InfuserBook") ui.open() + playsound(src, SFX_PAGE_TURN, 30, TRUE) + +/obj/item/infuser_book/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + if(action == "play_flip_sound") + playsound(src, SFX_PAGE_TURN, 30, TRUE) /obj/item/infuser_book/ui_static_data(mob/user) var/list/data = list() diff --git a/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm b/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm index 99431834d83..be1a3692455 100644 --- a/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm +++ b/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm @@ -75,3 +75,33 @@ infusion_desc = "skittish" tier = DNA_MUTANT_TIER_ONE status_effect_type = /datum/status_effect/organ_set_bonus/rat + +/datum/infuser_entry/roach + name = "Roach" + infuse_mob_name = "cockroach" + desc = "It seems as if you're a fan of ancient literature by your interest in this. Assuredly, merging cockroach DNA into your genome \ + will not cause you to become incapable of leaving your bed. These creatures are incredibly resilient against many things \ + humans are weak to, and we can use that! Who wouldn't like to survive a nuclear blast? \ + NOTE: Squished roaches will not work for the infuser, if that wasn't obvious. Try spraying them with some pestkiller from botany!" + threshold_desc = "you will no longer be gibbed by explosions, and gain incredible resistance to viruses and radiation." + qualities = list( + "resilience to attacks from behind", + "healthier organs", + "get over disgust very quickly", + "the ability to survive a nuclear apocalypse", + "harder to pick yourself up from falling over", + "avoid toxins at all costs", + "always down to find a snack", + ) + input_obj_or_mob = list( + /mob/living/basic/cockroach, + ) + output_organs = list( + /obj/item/organ/internal/heart/roach, + /obj/item/organ/internal/stomach/roach, + /obj/item/organ/internal/liver/roach, + /obj/item/organ/internal/appendix/roach, + ) + infusion_desc = "kafkaesque" // Gregor Samsa !! + tier = DNA_MUTANT_TIER_ONE + status_effect_type = /datum/status_effect/organ_set_bonus/roach diff --git a/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm new file mode 100644 index 00000000000..4aa96728358 --- /dev/null +++ b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm @@ -0,0 +1,226 @@ +#define ROACH_ORGAN_COLOR "#7c4200" +// Yeah i'm lazy and we don't use any of the other color slots +#define ROACH_COLORS ROACH_ORGAN_COLOR + ROACH_ORGAN_COLOR + ROACH_ORGAN_COLOR + +/datum/armor/roach_internal_armor + bomb = 100 + bio = 90 + +/datum/status_effect/organ_set_bonus/roach + id = "organ_set_bonus_roach" + organs_needed = 4 + bonus_activate_text = span_notice("Roach DNA is deeply infused with you! \ + You feel increasingly resistant to explosives, radiation, and viral agents.") + bonus_deactivate_text = span_notice("You are no longer majority roach, \ + and you feel much more vulnerable to nuclear apocalypses.") + // - Immunity to nuke gibs + // - Nukes come with radiation (not actually but yknow) + bonus_traits = list(TRAIT_NUKEIMMUNE, TRAIT_RADIMMUNE, TRAIT_VIRUS_RESISTANCE) + /// Armor type attached to the owner's physiology + var/datum/armor/given_armor = /datum/armor/roach_internal_armor + /// Storing biotypes pre-organ bonus applied so we don't remove bug from mobs which should have it. + var/old_biotypes = NONE + +/datum/status_effect/organ_set_bonus/roach/enable_bonus() + . = ..() + if(!ishuman(owner)) + return + + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.armor = human_owner.physiology.armor.add_other_armor(given_armor) + + old_biotypes = human_owner.mob_biotypes + human_owner.mob_biotypes |= MOB_BUG + +/datum/status_effect/organ_set_bonus/roach/disable_bonus() + . = ..() + if(!ishuman(owner) || QDELETED(owner)) + return + + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.armor = human_owner.physiology.armor.subtract_other_armor(given_armor) + + if(!(old_biotypes & MOB_BUG)) // only remove bug if it wasn't there before + human_owner.mob_biotypes &= ~MOB_BUG + +/// Roach heart: +/// Reduces damage taken from brute attacks from behind, +/// but increases duration of knockdowns +/obj/item/organ/internal/heart/roach + name = "mutated roach-heart" + desc = "Roach DNA infused into what was once a normal heart." + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + + icon = 'icons/obj/medical/organs/infuser_organs.dmi' + icon_state = "heart" + greyscale_config = /datum/greyscale_config/mutant_organ + greyscale_colors = ROACH_COLORS + + /// Timer ID for resetting the damage resistance applied from attacks from behind + var/defense_timerid + /// Bodypart overlay applied to the chest the heart is in + var/datum/bodypart_overlay/simple/roach_shell/roach_shell + +/obj/item/organ/internal/heart/roach/Initialize(mapload) + . = ..() + AddElement(/datum/element/noticable_organ, "has hardened, somewhat translucent skin.") + AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/roach) + roach_shell = new() + +/obj/item/organ/internal/heart/roach/Destroy() + QDEL_NULL(roach_shell) + return ..() + +/obj/item/organ/internal/heart/roach/on_insert(mob/living/carbon/organ_owner, special) + . = ..() + if(!ishuman(organ_owner)) + return + + var/mob/living/carbon/human/human_owner = organ_owner + + RegisterSignal(human_owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(modify_damage)) + human_owner.physiology.knockdown_mod *= 3 + + var/obj/item/bodypart/chest/chest = human_owner.get_bodypart(BODY_ZONE_CHEST) + chest.add_bodypart_overlay(roach_shell) + human_owner.update_body_parts() + +/obj/item/organ/internal/heart/roach/on_remove(mob/living/carbon/organ_owner, special) + . = ..() + if(!ishuman(organ_owner) || QDELETED(organ_owner)) + return + + var/mob/living/carbon/human/human_owner = organ_owner + + UnregisterSignal(human_owner, COMSIG_MOB_APPLY_DAMAGE) + human_owner.physiology.knockdown_mod /= 3 + + if(defense_timerid) + reset_damage(human_owner) + + var/obj/item/bodypart/chest/chest = human_owner.get_bodypart(BODY_ZONE_CHEST) + chest.remove_bodypart_overlay(roach_shell) + human_owner.update_body_parts() + +/** + * Signal proc for [COMSIG_MOB_APPLY_DAMAGE] + * + * Being hit with brute damage in the back will impart a large damage resistance bonus for a very short period. + */ +/obj/item/organ/internal/heart/roach/proc/modify_damage(datum/source, damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, obj/item/attacking_item) + SIGNAL_HANDLER + + if(!ishuman(owner) || !attack_direction || damagetype != BRUTE || owner.stat >= UNCONSCIOUS) + return + + var/mob/living/carbon/human/human_owner = owner + // No tactical spinning + if(human_owner.flags_1 & IS_SPINNING_1) + return + + // If we're lying down, or were attacked from the back, we get armor. + var/should_armor_up = (human_owner.body_position == LYING_DOWN) || (human_owner.dir & attack_direction) + if(!should_armor_up) + return + + // Take 50% less damage from attack behind us + if(!defense_timerid) + human_owner.physiology.brute_mod /= 2 + human_owner.visible_message(span_warning("[human_owner]'s back hardens against the blow!")) + playsound(human_owner, 'sound/effects/constructform.ogg', 25, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + + defense_timerid = addtimer(CALLBACK(src, PROC_REF(reset_damage), owner), 5 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + +/obj/item/organ/internal/heart/roach/proc/reset_damage(mob/living/carbon/human/human_owner) + defense_timerid = null + if(!QDELETED(human_owner)) + human_owner.physiology.brute_mod *= 2 + human_owner.visible_message(span_warning("[human_owner]'s back softens again.")) + +// Simple overlay so we can add a roach shell to guys with roach hearts +/datum/bodypart_overlay/simple/roach_shell + icon_state = "roach_shell" + layers = EXTERNAL_FRONT|EXTERNAL_BEHIND + +/datum/bodypart_overlay/simple/roach_shell/get_image(image_layer, obj/item/bodypart/limb) + return image( + icon = icon, + icon_state = "[icon_state]_[mutant_bodyparts_layertext(image_layer)]", + layer = image_layer, + ) + +/// Roach stomach: +/// Makes disgust a non-issue, very slightly worse at passing off reagents +/// Also makes you more hungry +/obj/item/organ/internal/stomach/roach + name = "mutated roach-stomach" + desc = "Roach DNA infused into what was once a normal stomach." + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + disgust_metabolism = 32 // Demolishes any disgust we have + metabolism_efficiency = 0.033 // Slightly worse at transferring reagents + hunger_modifier = 3 + + icon = 'icons/obj/medical/organs/infuser_organs.dmi' + icon_state = "stomach" + greyscale_config = /datum/greyscale_config/mutant_organ + greyscale_colors = ROACH_COLORS + +/obj/item/organ/internal/stomach/roach/Initialize(mapload) + . = ..() + AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/roach) + +/// Roach liver: +/// Purges toxins at a higher threshold, but takes more damage from them if not purged +/obj/item/organ/internal/liver/roach + name = "mutated roach-liver" + desc = "Roach DNA infused into what was once a normal liver." + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + toxTolerance = 5 // More tolerance for toxins + liver_resistance = 0.25 // But if they manage to get in you're screwed + + icon = 'icons/obj/medical/organs/infuser_organs.dmi' + icon_state = "liver" + greyscale_config = /datum/greyscale_config/mutant_organ + greyscale_colors = ROACH_COLORS + +/obj/item/organ/internal/liver/roach/Initialize(mapload) + . = ..() + AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/roach) + +/obj/item/organ/internal/liver/roach/on_insert(mob/living/carbon/organ_owner, special) + . = ..() + if(!ishuman(organ_owner)) + return + + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.tox_mod *= 2 + +/obj/item/organ/internal/liver/roach/on_remove(mob/living/carbon/organ_owner, special) + . = ..() + if(!ishuman(organ_owner) || QDELETED(organ_owner)) + return + + var/mob/living/carbon/human/human_owner = organ_owner + human_owner.physiology.tox_mod /= 2 + +/// Roach appendix: +/// No appendicitus! weee! +/obj/item/organ/internal/appendix/roach + name = "mutated roach-appendix" + desc = "Roach DNA infused into what was once a normal appendix. It could get worse?" + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + + icon = 'icons/obj/medical/organs/infuser_organs.dmi' + icon_state = "appendix" + greyscale_config = /datum/greyscale_config/mutant_organ + greyscale_colors = ROACH_COLORS + +/obj/item/organ/internal/appendix/roach/Initialize(mapload) + . = ..() + AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/roach) + +/obj/item/organ/internal/appendix/roach/become_inflamed() + return + +#undef ROACH_ORGAN_COLOR +#undef ROACH_COLORS diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm index a9555e6a1bb..12c06e6916a 100644 --- a/code/game/objects/items/his_grace.dm +++ b/code/game/objects/items/his_grace.dm @@ -159,22 +159,8 @@ if(!awakened) return - var/static/list/transforms - if(!transforms) - var/matrix/M1 = matrix() - var/matrix/M2 = matrix() - var/matrix/M3 = matrix() - var/matrix/M4 = matrix() - M1.Translate(-1, 0) - M2.Translate(0, 1) - M3.Translate(1, 0) - M4.Translate(0, -1) - transforms = list(M1, M2, M3, M4) - animate(src, transform=transforms[1], time=0.2, loop=-1) - animate(transform=transforms[2], time=0.1) - animate(transform=transforms[3], time=0.2) - animate(transform=transforms[4], time=0.3) + spasm_animation() /obj/item/his_grace/proc/drowse() //Good night, Mr. Grace. if(!awakened || ascended) diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm index 639227a8e96..a16414e0f6c 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm @@ -627,6 +627,9 @@ GLOBAL_VAR(station_nuke_source) * Helper proc that handles gibbing someone who has been nuked. */ /proc/nuke_gib(mob/living/gibbed, atom/source) + if(HAS_TRAIT(gibbed, TRAIT_NUKEIMMUNE)) + return FALSE + if(istype(gibbed.loc, /obj/structure/closet/secure_closet/freezer)) var/obj/structure/closet/secure_closet/freezer/freezer = gibbed.loc if(!freezer.jones) diff --git a/code/modules/mob/living/basic/vermin/cockroach.dm b/code/modules/mob/living/basic/vermin/cockroach.dm index 32df5e915ec..d7b2d8aade5 100644 --- a/code/modules/mob/living/basic/vermin/cockroach.dm +++ b/code/modules/mob/living/basic/vermin/cockroach.dm @@ -2,13 +2,15 @@ name = "cockroach" desc = "This station is just crawling with bugs." icon_state = "cockroach" - icon_dead = "cockroach" //Make this work + icon_dead = "cockroach_no_animation" density = FALSE mob_biotypes = MOB_ORGANIC|MOB_BUG mob_size = MOB_SIZE_TINY + held_w_class = WEIGHT_CLASS_TINY health = 1 maxHealth = 1 speed = 1.25 + can_be_held = TRUE gold_core_spawnable = FRIENDLY_SPAWN pass_flags = PASSTABLE | PASSGRILLE | PASSMOB @@ -38,17 +40,23 @@ var/static/list/roach_drops = list(/obj/effect/decal/cleanable/insectguts) AddElement(/datum/element/death_drops, roach_drops) AddElement(/datum/element/swabable, cockroach_cell_line, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 7) - AddComponent(/datum/component/squashable, squash_chance = 50, squash_damage = 1) + AddElement(/datum/element/basic_body_temp_sensitive, 270, INFINITY) + AddComponent( \ + /datum/component/squashable, \ + squash_chance = 50, \ + squash_damage = 1, \ + squash_flags = SQUASHED_SHOULD_BE_GIBBED|SQUASHED_ALWAYS_IF_DEAD|SQUASHED_DONT_SQUASH_IN_CONTENTS, \ + ) ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - -/mob/living/basic/cockroach/death(gibbed) - if(GLOB.station_was_nuked) //If the nuke is going off, then cockroaches are invincible. Keeps the nuke from killing them, cause cockroaches are immune to nukes. - return - return ..() + ADD_TRAIT(src, TRAIT_NUKEIMMUNE, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_RADIMMUNE, INNATE_TRAIT) /mob/living/basic/cockroach/ex_act() //Explosions are a terrible way to handle a cockroach. return FALSE +// Roach goop is the gibs to drop +/mob/living/basic/cockroach/spawn_gibs() + return /datum/ai_controller/basic_controller/cockroach blackboard = list( @@ -125,7 +133,13 @@ /mob/living/basic/cockroach/hauberoach/Initialize(mapload) . = ..() AddComponent(/datum/component/caltrop, min_damage = 10, max_damage = 15, flags = (CALTROP_BYPASS_SHOES | CALTROP_SILENT)) - AddComponent(/datum/component/squashable, squash_chance = 100, squash_damage = 1, squash_callback = TYPE_PROC_REF(/mob/living/basic/cockroach/hauberoach, on_squish)) + AddComponent( \ + /datum/component/squashable, \ + squash_chance = 100, \ + squash_damage = 1, \ + squash_flags = SQUASHED_SHOULD_BE_GIBBED|SQUASHED_ALWAYS_IF_DEAD|SQUASHED_DONT_SQUASH_IN_CONTENTS, \ + squash_callback = TYPE_PROC_REF(/mob/living/basic/cockroach/hauberoach, on_squish), \ + ) ///Proc used to override the squashing behavior of the normal cockroach. /mob/living/basic/cockroach/hauberoach/proc/on_squish(mob/living/cockroach, mob/living/living_target) @@ -136,6 +150,7 @@ return TRUE living_target.visible_message(span_notice("[living_target] squashes [cockroach], not even noticing its spike."), span_notice("You squashed [cockroach], not even noticing its spike.")) return FALSE + /datum/ai_controller/basic_controller/cockroach/hauberoach planning_subtrees = list( /datum/ai_planning_subtree/pet_planning, diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index 333e318f9e0..e99aa7a00aa 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -65,9 +65,9 @@ if(stage < 6) INVOKE_ASYNC(src, PROC_REF(RefreshInfectionImage)) var/slowdown = 1 - if(ishuman(owner)) - var/mob/living/carbon/human/baby_momma = owner - slowdown = baby_momma.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) ? 2 : 1 // spaceacillin doubles the time it takes to grow + if(!isnull(owner)) // it gestates out of bodies. + if(HAS_TRAIT(owner, TRAIT_VIRUS_RESISTANCE)) + slowdown *= 2 // spaceacillin doubles the time it takes to grow if(owner.has_status_effect(/datum/status_effect/nest_sustenance)) slowdown *= 0.80 //egg gestates 20% faster if you're trapped in a nest diff --git a/code/modules/mob/living/carbon/human/physiology.dm b/code/modules/mob/living/carbon/human/physiology.dm index b2998db26ac..bfd3dc8d73c 100644 --- a/code/modules/mob/living/carbon/human/physiology.dm +++ b/code/modules/mob/living/carbon/human/physiology.dm @@ -16,7 +16,11 @@ var/siemens_coeff = 1 // resistance to shocks - var/stun_mod = 1 // % stun modifier + /// Multiplier applied to all incapacitating stuns (knockdown, stun, paralyze, immobilize) + var/stun_mod = 1 + /// Multiplied aplpied to just knockdowns, stacks with above multiplicatively + var/knockdown_mod = 1 + var/bleed_mod = 1 // % bleeding modifier var/datum/armor/armor // internal armor datum diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm index d09287ef4f2..166cefbde6a 100644 --- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm @@ -32,13 +32,6 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest/fly, ) -/datum/species/fly/handle_chemical(datum/reagent/chem, mob/living/carbon/human/affected, seconds_per_tick, times_fired) - . = ..() - if(. & COMSIG_MOB_STOP_REAGENT_CHECK) - return - if(chem.type == /datum/reagent/toxin/pestkiller) - affected.adjustToxLoss(3 * REM * seconds_per_tick) - /datum/species/fly/check_species_weakness(obj/item/weapon, mob/living/attacker) if(istype(weapon, /obj/item/melee/flyswatter)) return 30 //Flyswatters deal 30x damage to flypeople. diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index b88481e3695..20725585de6 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -46,19 +46,11 @@ return randname -/datum/species/moth/handle_chemical(datum/reagent/chem, mob/living/carbon/human/affected, seconds_per_tick, times_fired) - . = ..() - if(. & COMSIG_MOB_STOP_REAGENT_CHECK) - return - if(chem.type == /datum/reagent/toxin/pestkiller) - affected.adjustToxLoss(3 * REM * seconds_per_tick) - /datum/species/moth/check_species_weakness(obj/item/weapon, mob/living/attacker) if(istype(weapon, /obj/item/melee/flyswatter)) return 10 //flyswatters deal 10x damage to moths return 1 - /datum/species/moth/randomize_features(mob/living/carbon/human/human_mob) human_mob.dna.features["moth_markings"] = pick(GLOB.moth_markings_list) randomize_external_organs(human_mob) diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm index 8f1077fcb63..8f6dc8efeb9 100644 --- a/code/modules/mob/living/carbon/human/status_procs.dm +++ b/code/modules/mob/living/carbon/human/status_procs.dm @@ -1,10 +1,10 @@ /mob/living/carbon/human/Stun(amount, ignore_canstun = FALSE) - amount = dna.species.spec_stun(src,amount) + amount = dna.species.spec_stun(src, amount) return ..() /mob/living/carbon/human/Knockdown(amount, ignore_canstun = FALSE) - amount = dna.species.spec_stun(src,amount) + amount = dna.species.spec_stun(src, amount) * physiology.knockdown_mod return ..() /mob/living/carbon/human/Paralyze(amount, ignore_canstun = FALSE) @@ -16,7 +16,7 @@ return ..() /mob/living/carbon/human/Unconscious(amount, ignore_canstun = FALSE) - amount = dna.species.spec_stun(src,amount) + amount = dna.species.spec_stun(src, amount) if(HAS_TRAIT(src, TRAIT_HEAVY_SLEEPER)) amount *= (rand(125, 130) * 0.01) return ..() diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index 6186d06b81c..497bb460af0 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -2002,17 +2002,18 @@ taste_description = "the pain of ten thousand slain mosquitos" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/consumable/ethanol/bug_spray/on_new(data) + . = ..() + AddElement(/datum/element/bugkiller_reagent) + /datum/reagent/consumable/ethanol/bug_spray/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) - //Bugs should not drink Bug spray. - if(ismoth(drinker) || isflyperson(drinker)) - drinker.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - return ..() - -/datum/reagent/consumable/ethanol/bug_spray/on_mob_metabolize(mob/living/carbon/drinker) - - if(ismoth(drinker) || isflyperson(drinker)) + // Does some damage to bug biotypes + var/did_damage = drinker.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = MOB_BUG) + // Random chance of causing a screm if we did some damage + if(did_damage && SPT_PROB(2, seconds_per_tick)) drinker.emote("scream") - return ..() + + return ..() || did_damage /datum/reagent/consumable/ethanol/applejack name = "Applejack" diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 676a49d59b4..004bab25538 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -261,6 +261,14 @@ ph = 8.1 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/medicine/spaceacillin/on_mob_metabolize(mob/living/L) + . = ..() + ADD_TRAIT(L, TRAIT_VIRUS_RESISTANCE, type) + +/datum/reagent/medicine/spaceacillin/on_mob_end_metabolize(mob/living/L) + . = ..() + REMOVE_TRAIT(L, TRAIT_VIRUS_RESISTANCE, type) + //Goon Chems. Ported mainly from Goonstation. Easily mixable (or not so easily) and provide a variety of effects. /datum/reagent/medicine/oxandrolone diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index 56734add996..76e97b5e0b0 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -401,17 +401,19 @@ ph = 3.2 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/toxin/pestkiller/on_new(data) + . = ..() + AddElement(/datum/element/bugkiller_reagent) + +/datum/reagent/toxin/pestkiller/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = affected_mob.adjustToxLoss(2 * toxpwr * REM * seconds_per_tick, updating_health = FALSE, required_biotype = MOB_BUG) + return ..() || . + //Pest Spray /datum/reagent/toxin/pestkiller/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) mytray.adjust_toxic(round(volume)) mytray.adjust_pestlevel(-rand(1,2)) -/datum/reagent/toxin/pestkiller/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) - . = ..() - if(exposed_mob.mob_biotypes & MOB_BUG) - var/damage = min(round(0.4*reac_volume, 0.1),10) - exposed_mob.adjustToxLoss(damage, required_biotype = affected_biotype) - /datum/reagent/toxin/pestkiller/organic name = "Natural Pest Killer" description = "An organic mixture used to kill pests, with less of the side effects. Do not ingest!" diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm old mode 100644 new mode 100755 diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm index 620796333a9..9d3a298812e 100644 --- a/code/modules/zombie/items.dm +++ b/code/modules/zombie/items.dm @@ -30,7 +30,7 @@ return // spaceacillin has a 75% chance to block infection - if(istype(target) && target.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) && prob(75)) + if(HAS_TRAIT(target, TRAIT_VIRUS_RESISTANCE) && prob(75)) return var/obj/item/organ/internal/zombie_infection/infection diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi index 53b76835735..353fee69b06 100644 Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ diff --git a/icons/mob/species/misc/bodypart_overlay_simple.dmi b/icons/mob/species/misc/bodypart_overlay_simple.dmi index 2bc1dda5663..2c1739fd168 100644 Binary files a/icons/mob/species/misc/bodypart_overlay_simple.dmi and b/icons/mob/species/misc/bodypart_overlay_simple.dmi differ diff --git a/icons/obj/medical/organs/infuser_organs.dmi b/icons/obj/medical/organs/infuser_organs.dmi index 49ac2751aae..c2551b41f66 100644 Binary files a/icons/obj/medical/organs/infuser_organs.dmi and b/icons/obj/medical/organs/infuser_organs.dmi differ diff --git a/tgstation.dme b/tgstation.dme index eb82ef338f3..7fd54ca73ef 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1175,6 +1175,7 @@ #include "code\datums\elements\befriend_petting.dm" #include "code\datums\elements\blocks_explosives.dm" #include "code\datums\elements\bsa_blocker.dm" +#include "code\datums\elements\bugkiller_reagent.dm" #include "code\datums\elements\bump_click.dm" #include "code\datums\elements\can_barricade.dm" #include "code\datums\elements\caseless.dm" @@ -1691,6 +1692,7 @@ #include "code\game\machinery\dna_infuser\organ_sets\goliath_organs.dm" #include "code\game\machinery\dna_infuser\organ_sets\gondola_organs.dm" #include "code\game\machinery\dna_infuser\organ_sets\rat_organs.dm" +#include "code\game\machinery\dna_infuser\organ_sets\roach_organs.dm" #include "code\game\machinery\doors\airlock.dm" #include "code\game\machinery\doors\airlock_electronics.dm" #include "code\game\machinery\doors\airlock_types.dm" diff --git a/tgui/packages/tgui/interfaces/InfuserBook.tsx b/tgui/packages/tgui/interfaces/InfuserBook.tsx index ae3d81b96af..20eeb8bc3a1 100644 --- a/tgui/packages/tgui/interfaces/InfuserBook.tsx +++ b/tgui/packages/tgui/interfaces/InfuserBook.tsx @@ -28,7 +28,7 @@ type TierData = { name: string; }; -const PAGE_HEIGHT = '235px'; +const PAGE_HEIGHT = 30; const TIER2TIERDATA: TierData[] = [ { @@ -69,7 +69,7 @@ const TIER2TIERDATA: TierData[] = [ ]; export const InfuserBook = (props, context) => { - const { data } = useBackend(context); + const { data, act } = useBackend(context); const { entries } = data; const [bookPosition, setBookPosition] = useLocalState( @@ -87,7 +87,11 @@ export const InfuserBook = (props, context) => { let currentEntry = paginatedEntries[chapter][pageInChapter]; const switchChapter = (newChapter) => { + if (chapter === newChapter) { + return; + } setBookPosition({ chapter: newChapter, pageInChapter: 0 }); + act('play_flip_sound'); // just so we can play a sound fx on page turn }; const setPage = (newPage) => { @@ -112,6 +116,7 @@ export const InfuserBook = (props, context) => { newBookPosition.pageInChapter = newPage; } setBookPosition(newBookPosition); + act('play_flip_sound'); // just so we can play a sound fx on page turn }; const tabs = [ @@ -127,7 +132,7 @@ export const InfuserBook = (props, context) => { const restrictedNext = chapter === 3 && pageInChapter === 0; return ( - + @@ -215,7 +220,7 @@ export const InfuserInstructions = (props, context) => {
3. Have someone activate the machine externally.
- + And you're done! Note that the infusion source will be obliterated in the process.