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.