diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm
index 7b7cec2c72a05..35492d9e9f2e0 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm
@@ -63,12 +63,7 @@
/obj/item/cult_shift,
/obj/effect/decal/remains/human,
/obj/item/melee/cultblade/dagger,
-/obj/effect/step_trigger/sound_effect{
- happens_once = 1;
- name = "\proper a grave mistake";
- sound = 'sound/effects/hallucinations/i_see_you1.ogg';
- triggerer_only = 1
- },
+/obj/effect/step_trigger/sound_effect/lavaland_cult_altar,
/obj/effect/step_trigger/message{
message = "You've made a grave mistake, haven't you?";
name = "ohfuck"
diff --git a/_maps/RandomRuins/SpaceRuins/hauntedtradingpost.dmm b/_maps/RandomRuins/SpaceRuins/hauntedtradingpost.dmm
index df3c3ca64277e..9efa8cd365454 100644
--- a/_maps/RandomRuins/SpaceRuins/hauntedtradingpost.dmm
+++ b/_maps/RandomRuins/SpaceRuins/hauntedtradingpost.dmm
@@ -5468,11 +5468,9 @@
/area/ruin/space/has_grav/hauntedtradingpost/employees/breakroom)
"Vl" = (
/obj/structure/table/wood,
-/obj/item/toy/figure/wizard{
+/obj/item/toy/figure/wizard/special{
pixel_y = 9;
pixel_x = -4;
- toysay = "CLANG!";
- toysound = 'sound/effects/clang.ogg'
},
/obj/item/toy/figure/warden{
name = "\improper Knight action figure";
diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm
index 2dfbf9a55ad48..8eca8d06b6112 100644
--- a/_maps/map_files/wawastation/wawastation.dmm
+++ b/_maps/map_files/wawastation/wawastation.dmm
@@ -4937,9 +4937,9 @@
/turf/open/floor/iron/dark,
/area/station/security/courtroom)
"bLI" = (
-/obj/machinery/processor/slime,
/obj/effect/turf_decal/bot_red,
/obj/effect/turf_decal/stripes/line,
+/obj/machinery/processor/slime,
/turf/open/floor/iron/white/textured_large,
/area/station/science/xenobiology)
"bLS" = (
@@ -46862,6 +46862,7 @@
/obj/effect/turf_decal/stripes/line{
dir = 1
},
+/obj/item/stack/sheet/mineral/plasma,
/turf/open/floor/iron/white/textured_large,
/area/station/science/xenobiology)
"qGN" = (
@@ -47527,11 +47528,11 @@
/turf/open/floor/plating,
/area/station/maintenance/department/engine)
"qUi" = (
-/obj/machinery/processor/slime,
/obj/effect/turf_decal/bot_red,
/obj/effect/turf_decal/stripes/line{
dir = 1
},
+/obj/machinery/processor/slime,
/turf/open/floor/iron/white/textured_large,
/area/station/science/xenobiology)
"qUl" = (
@@ -48756,6 +48757,7 @@
pixel_y = 8
},
/obj/effect/turf_decal/stripes/line,
+/obj/item/stack/sheet/mineral/plasma,
/turf/open/floor/iron/white/textured_large,
/area/station/science/xenobiology)
"rms" = (
diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm
index bd1b7933e861a..5bfa7fdff6374 100644
--- a/code/__DEFINES/DNA.dm
+++ b/code/__DEFINES/DNA.dm
@@ -60,6 +60,7 @@
#define DNA_MOTH_MARKINGS_BLOCK 13
#define DNA_MUSHROOM_CAPS_BLOCK 14
#define DNA_POD_HAIR_BLOCK 15
+#define DNA_FISH_TAIL_BLOCK 16
// Hey! Listen up if you're here because you're adding a species feature!
//
@@ -68,7 +69,7 @@
// (Which means having a DNA block for a feature tied to a mob without DNA is entirely pointless.)
/// Total amount of DNA blocks, must be equal to the highest DNA block number
-#define DNA_FEATURE_BLOCKS 15
+#define DNA_FEATURE_BLOCKS 16
#define DNA_SEQUENCE_LENGTH 4
#define DNA_MUTATION_BLOCKS 8
diff --git a/code/__DEFINES/alerts.dm b/code/__DEFINES/alerts.dm
index 17db402c6c04d..ad68b1d55959d 100644
--- a/code/__DEFINES/alerts.dm
+++ b/code/__DEFINES/alerts.dm
@@ -14,6 +14,8 @@
#define ALERT_TOO_MUCH_NITRO "too_much_nitro"
#define ALERT_NOT_ENOUGH_NITRO "not_enough_nitro"
+#define ALERT_NOT_ENOUGH_WATER "not_enough_water"
+
/** Mob related */
#define ALERT_SUCCUMB "succumb"
#define ALERT_BUCKLED "buckled"
diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm
index 81224e7a58d81..5565c143d6613 100644
--- a/code/__DEFINES/dcs/signals/signals_datum.dm
+++ b/code/__DEFINES/dcs/signals/signals_datum.dm
@@ -48,3 +48,6 @@
///from /datum/component/on_hit_effect/send_signal(): (user, target, hit_zone)
#define COMSIG_ON_HIT_EFFECT "comsig_on_hit_effect"
+
+///from /datum/component/bubble_icon_override/get_bubble_icon(): (list/holder)
+#define COMSIG_GET_BUBBLE_ICON "get_bubble_icon"
diff --git a/code/__DEFINES/dcs/signals/signals_fish.dm b/code/__DEFINES/dcs/signals/signals_fish.dm
index 9b614f924549f..f7606b63a01db 100644
--- a/code/__DEFINES/dcs/signals/signals_fish.dm
+++ b/code/__DEFINES/dcs/signals/signals_fish.dm
@@ -24,8 +24,6 @@
#define COMSIG_FISH_EATEN_BY_OTHER_FISH "fish_eaten_by_other_fish"
///From /obj/item/fish/generate_reagents_to_add, which returns a holder when the fish is eaten or composted for example: (list/reagents)
#define COMSIG_GENERATE_REAGENTS_TO_ADD "generate_reagents_to_add"
-///From /obj/item/fish/feed: (fed_reagents, fed_reagent_type)
-#define COMSIG_FISH_FED "fish_on_fed"
///From /obj/item/fish/update_size_and_weight: (new_size, new_weight)
#define COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT "fish_update_size_and_weight"
///From /obj/item/fish/update_fish_force: (weight_rank, bonus_malus)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index 285c0b0ac4e2a..633282001f67a 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -272,6 +272,9 @@
#define COMSIG_LIVING_GRAB "living_grab"
// Return COMPONENT_CANCEL_ATTACK_CHAIN / COMPONENT_SKIP_ATTACK_CHAIN to stop the grab
+/// From /datum/component/edible/get_perceived_food_quality(): (datum/component/edible/edible, list/extra_quality)
+#define COMSIG_LIVING_GET_PERCEIVED_FOOD_QUALITY "get_perceived_food_quality"
+
///Called when living finish eat (/datum/component/edible/proc/On_Consume)
#define COMSIG_LIVING_FINISH_EAT "living_finish_eat"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index f0368ac8d92df..df9b7cc22960e 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -376,6 +376,8 @@
#define SLIP_WHEN_CRAWLING (1<<4)
/// the mob won't slip if the turf has the TRAIT_TURF_IGNORE_SLIPPERY trait.
#define SLIPPERY_TURF (1<<5)
+/// For mobs who are slippery, this requires the mob holding it to be lying down.
+#define SLIPPERY_WHEN_LYING_DOWN (1<<6)
#define MAX_CHICKENS 50
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index eacdd868a9556..80c316f3585a9 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -122,3 +122,7 @@
/// Meaning that if the message is visual, and sourced from a blind mob, they will not see it.
/// This flag skips that behavior, and will always show the self message to the mob.
#define ALWAYS_SHOW_SELF_MESSAGE (1<<1)
+
+///Defines for priorities for the bubble_icon_override comp
+#define BUBBLE_ICON_PRIORITY_ACCESSORY 2
+#define BUBBLE_ICON_PRIORITY_ORGAN 1
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index 0fbdc0d44b2b6..941aa99562457 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -32,6 +32,7 @@
#define INTERACTION_SOUND_RANGE_MODIFIER -3
#define EQUIP_SOUND_VOLUME 30
+#define LIQUID_SLOSHING_SOUND_VOLUME 10
#define PICKUP_SOUND_VOLUME 15
#define DROP_SOUND_VOLUME 20
#define YEET_SOUND_VOLUME 90
@@ -189,3 +190,6 @@ GLOBAL_LIST_INIT(announcer_keys, list(
#define SFX_LIQUID_POUR "liquid_pour"
#define SFX_SNORE_FEMALE "snore_female"
#define SFX_SNORE_MALE "snore_male"
+#define SFX_PLASTIC_BOTTLE_LIQUID_SLOSH "plastic_bottle_liquid_slosh"
+#define SFX_DEFAULT_LIQUID_SLOSH "default_liquid_slosh"
+#define SFX_PLATE_ARMOR_RUSTLE "plate_armor_rustle"
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 06f1bd6764b2a..70d5ab94d5f65 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -185,6 +185,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_DISGUISED "disguised"
/// Use when you want a mob to be able to metabolize plasma temporarily (e.g. plasma fixation disease symptom)
#define TRAIT_PLASMA_LOVER_METABOLISM "plasma_lover_metabolism"
+/// The mob is not harmed by tetrodotoxin. Instead, it heals them like omnizine
+#define TRAIT_TETRODOTOXIN_HEALING "tetrodotoxin_healing"
#define TRAIT_EASYDISMEMBER "easy_dismember"
#define TRAIT_LIMBATTACHMENT "limb_attach"
#define TRAIT_NOLIMBDISABLE "no_limb_disable"
@@ -247,6 +249,16 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION "message_in_a_bottle_location"
/// Stops other objects of the same type from being inserted inside the same aquarium it's in.
#define TRAIT_UNIQUE_AQUARIUM_CONTENT "unique_aquarium_content"
+/// Mobs that hate showers, being sprayed with water etc.
+#define TRAIT_WATER_HATER "water_hater"
+/// Improved boons from showers and some features centered around water, should also suppress TRAIT_WATER_HATER
+#define TRAIT_WATER_ADAPTATION "water_adaptation"
+/// Tells us that the mob urrently has the fire_handler/wet_stacks status effect
+#define TRAIT_IS_WET "is_wet"
+/// Mobs with this trait stay wet for longer and resist fire decaying wetness
+#define TRAIT_WET_FOR_LONGER "wet_for_longer"
+/// Mobs with this trait will be immune to slipping while also being slippery themselves when lying on the floor
+#define TRAIT_SLIPPERY_WHEN_WET "slippery_when_wet"
/// This trait lets you evaluate someone's fitness level against your own
#define TRAIT_EXAMINE_FITNESS "reveal_power_level"
/// These mobs have particularly hygienic tongues
@@ -724,6 +736,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_HONKSPAMMING "trait_honkspamming"
/// Required by the waddling element since there are multiple sources of it.
#define TRAIT_WADDLING "trait_waddling"
+/// Mobs with trait will still waddle even when lying on the floor and make a different footstep sound when doing so.
+#define TRAIT_FLOPPING "trait_flopping"
/// Required by the on_hit_effect element, which is in turn added by other elements.
#define TRAIT_ON_HIT_EFFECT "trait_on_hit_effect"
@@ -836,8 +850,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_MUSICIAN "musician"
#define TRAIT_LIGHT_DRINKER "light_drinker"
#define TRAIT_EMPATH "empath"
+#define TRAIT_EVIL "evil"
#define TRAIT_FRIENDLY "friendly"
#define TRAIT_GRABWEAKNESS "grab_weakness"
+#define TRAIT_GRABRESISTANCE "grab_resistance"
#define TRAIT_SNOB "snob"
#define TRAIT_BALD "bald"
#define TRAIT_SHAVED "shaved"
@@ -994,6 +1010,15 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_FISH_SHOULD_TWOHANDED "fish_should_twohanded"
///This fish won't be killed when cooked.
#define TRAIT_FISH_SURVIVE_COOKING "fish_survive_cooking"
+/**
+ * This fish has been fed teslium without the electrogenesis having trait.
+ * Gives the electrogenesis, but at halved output, and it hurts the fish over time.
+ */
+#define TRAIT_FISH_ON_TESLIUM "fish_on_teslium"
+/// This fish has been fed growth serum or something and will grow 5 times faster, up to 50% weight and size gain when fed.
+#define TRAIT_FISH_QUICK_GROWTH "fish_quick_growth"
+/// This fish has been fed mutagen or something. Evolutions will have more than twice the probability
+#define TRAIT_FISH_MUTAGENIC "fish_mutagenic"
/// Trait given to angelic constructs to let them purge cult runes
#define TRAIT_ANGELIC "angelic"
@@ -1070,6 +1095,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define STATION_TRAIT_UNIQUE_AI "station_trait_unique_ai"
#define STATION_TRAIT_UNNATURAL_ATMOSPHERE "station_trait_unnatural_atmosphere"
#define STATION_TRAIT_VENDING_SHORTAGE "station_trait_vending_shortage"
+#define STATION_TRAIT_SPIKED_DRINKS "station_trait_spiked_drinks"
///Deathmatch traits
#define TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS "deathmath_explosive_implants"
@@ -1295,6 +1321,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
///Trait which allows mobs to parry mining mob projectiles
#define TRAIT_MINING_PARRYING "mining_parrying"
+///Trait which silences all chemical reactions in its container
+#define TRAIT_SILENT_REACTIONS "silent_reactions"
+
/**
*
* This trait is used in some interactions very high in the interaction chain to allow
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 3eb89831957b8..92c725bd81bb7 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -302,7 +302,7 @@
* Tips that starts with the @ character won't be html encoded. That's necessary for any tip containing markup tags,
* just make sure they don't also have html characters like <, > and ' which will be garbled.
*/
-/proc/send_tip_of_the_round(target, selected_tip)
+/proc/send_tip_of_the_round(target, selected_tip, source = "Tip of the round")
var/message
if(selected_tip)
message = selected_tip
@@ -320,4 +320,4 @@
message = html_encode(message)
else
message = copytext(message, 2)
- to_chat(target, span_purple(examine_block("Tip of the round: [message]")))
+ to_chat(target, span_purple(examine_block("[source]: [message]")))
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 29e3ea84f1465..5976ddb05948f 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -77,6 +77,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_WADDLING" = TRAIT_WADDLING,
"TRAIT_WAS_RENAMED" = TRAIT_WAS_RENAMED,
"TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE,
+ "TRAIT_SILENT_REACTIONS" = TRAIT_SILENT_REACTIONS,
),
/datum/controller/subsystem/economy = list(
"TRAIT_MARKET_CRASHING" = TRAIT_MARKET_CRASHING,
@@ -111,6 +112,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"STATION_TRAIT_UNIQUE_AI" = STATION_TRAIT_UNIQUE_AI,
"STATION_TRAIT_UNNATURAL_ATMOSPHERE" = STATION_TRAIT_UNNATURAL_ATMOSPHERE,
"STATION_TRAIT_VENDING_SHORTAGE" = STATION_TRAIT_VENDING_SHORTAGE,
+ "STATION_TRAIT_SPIKED_DRINKS" = STATION_TRAIT_SPIKED_DRINKS,
),
/datum/deathmatch_lobby = list(
"TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS" = TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS,
@@ -227,6 +229,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_EMOTEMUTE" = TRAIT_EMOTEMUTE,
"TRAIT_EMPATH" = TRAIT_EMPATH,
"TRAIT_ENTRAILS_READER" = TRAIT_ENTRAILS_READER,
+ "TRAIT_EVIL" = TRAIT_EVIL,
"TRAIT_EXAMINE_DEEPER_FISH" = TRAIT_EXAMINE_DEEPER_FISH,
"TRAIT_EXAMINE_FISH" = TRAIT_EXAMINE_FISH,
"TRAIT_EXAMINE_FISHING_SPOT" = TRAIT_EXAMINE_FISHING_SPOT,
@@ -247,6 +250,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_FIXED_MUTANT_COLORS" = TRAIT_FIXED_MUTANT_COLORS,
"TRAIT_FLESH_DESIRE" = TRAIT_FLESH_DESIRE,
"TRAIT_FLOORED" = TRAIT_FLOORED,
+ "TRAIT_FLOPPING" = TRAIT_FLOPPING,
"TRAIT_FORBID_MINING_SHUTTLE_CONSOLE_OUTSIDE_STATION" = TRAIT_FORBID_MINING_SHUTTLE_CONSOLE_OUTSIDE_STATION,
"TRAIT_FORCED_GRAVITY" = TRAIT_FORCED_GRAVITY,
"TRAIT_FORCED_STANDING" = TRAIT_FORCED_STANDING,
@@ -264,6 +268,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_GIANT" = TRAIT_GIANT,
"TRAIT_GODMODE" = TRAIT_GODMODE,
"TRAIT_GOOD_HEARING" = TRAIT_GOOD_HEARING,
+ "TRAIT_GRABRESISTANCE" = TRAIT_GRABRESISTANCE,
"TRAIT_GRABWEAKNESS" = TRAIT_GRABWEAKNESS,
"TRAIT_GREENTEXT_CURSED" = TRAIT_GREENTEXT_CURSED,
"TRAIT_GUNFLIP" = TRAIT_GUNFLIP,
@@ -299,6 +304,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_INVISIBLE_MAN" = TRAIT_INVISIBLE_MAN,
"TRAIT_INVISIMIN" = TRAIT_INVISIMIN,
"TRAIT_IN_CALL" = TRAIT_IN_CALL,
+ "TRAIT_IS_WET" = TRAIT_IS_WET,
"TRAIT_IWASBATONED" = TRAIT_IWASBATONED,
"TRAIT_JOLLY" = TRAIT_JOLLY,
"TRAIT_KISS_OF_DEATH" = TRAIT_KISS_OF_DEATH,
@@ -462,6 +468,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE,
"TRAIT_SKITTISH" = TRAIT_SKITTISH,
"TRAIT_SLEEPIMMUNE" = TRAIT_SLEEPIMMUNE,
+ "TRAIT_SLIPPERY_WHEN_WET" = TRAIT_SLIPPERY_WHEN_WET,
"TRAIT_SMOKER" = TRAIT_SMOKER,
"TRAIT_SNEAK" = TRAIT_SNEAK,
"TRAIT_SNOB" = TRAIT_SNOB,
@@ -502,6 +509,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_TENACIOUS" = TRAIT_TENACIOUS,
"TRAIT_TENTACLE_IMMUNE" = TRAIT_TENTACLE_IMMUNE,
"TRAIT_TESLA_SHOCKIMMUNE" = TRAIT_TESLA_SHOCKIMMUNE,
+ "TRAIT_TETRODOTOXIN_HEALING" = TRAIT_TETRODOTOXIN_HEALING,
"TRAIT_THERMAL_VISION" = TRAIT_THERMAL_VISION,
"TRAIT_THINKING_IN_CHARACTER" = TRAIT_THINKING_IN_CHARACTER,
"TRAIT_THROWINGARM" = TRAIT_THROWINGARM,
@@ -535,9 +543,12 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_VIRUS_RESISTANCE" = TRAIT_VIRUS_RESISTANCE,
"TRAIT_VORACIOUS" = TRAIT_VORACIOUS,
"TRAIT_WAS_EVOLVED" = TRAIT_WAS_EVOLVED,
+ "TRAIT_WATER_ADAPTATION" = TRAIT_WATER_ADAPTATION,
+ "TRAIT_WATER_HATER" = TRAIT_WATER_HATER,
"TRAIT_WEAK_SOUL" = TRAIT_WEAK_SOUL,
"TRAIT_WEB_SURFER" = TRAIT_WEB_SURFER,
"TRAIT_WEB_WEAVER" = TRAIT_WEB_WEAVER,
+ "TRAIT_WET_FOR_LONGER" = TRAIT_WET_FOR_LONGER,
"TRAIT_WINE_TASTER" = TRAIT_WINE_TASTER,
"TRAIT_WING_BUFFET" = TRAIT_WING_BUFFET,
"TRAIT_WING_BUFFET_TIRED" = TRAIT_WING_BUFFET_TIRED,
@@ -619,14 +630,17 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_FISH_FLOPPING" = TRAIT_FISH_FLOPPING,
"TRAIT_FISH_FROM_CASE" = TRAIT_FISH_FROM_CASE,
"TRAIT_FISH_INK_ON_COOLDOWN" = TRAIT_FISH_INK_ON_COOLDOWN,
+ "TRAIT_FISH_MUTAGENIC" = TRAIT_FISH_MUTAGENIC,
"TRAIT_FISH_NO_HUNGER" = TRAIT_FISH_NO_HUNGER,
"TRAIT_FISH_NO_MATING" = TRAIT_FISH_NO_MATING,
+ "TRAIT_FISH_ON_TESLIUM" = TRAIT_FISH_ON_TESLIUM,
"TRAIT_FISH_RECESSIVE" = TRAIT_FISH_RECESSIVE,
"TRAIT_FISH_SELF_REPRODUCE" = TRAIT_FISH_SELF_REPRODUCE,
"TRAIT_FISH_SHOULD_TWOHANDED" = TRAIT_FISH_SHOULD_TWOHANDED,
"TRAIT_FISH_STASIS" = TRAIT_FISH_STASIS,
"TRAIT_FISH_STINGER" = TRAIT_FISH_STINGER,
"TRAIT_FISH_SURVIVE_COOKING" = TRAIT_FISH_SURVIVE_COOKING,
+ "TRAIT_FISH_QUICK_GROWTH" = TRAIT_FISH_QUICK_GROWTH,
"TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE,
"TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY,
"TRAIT_FISH_WELL_COOKED" = TRAIT_FISH_WELL_COOKED,
diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm
index 335e35a7111ec..bc41de2baf481 100644
--- a/code/_globalvars/traits/admin_tooling.dm
+++ b/code/_globalvars/traits/admin_tooling.dm
@@ -22,6 +22,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_SCARY_FISHERMAN" = TRAIT_SCARY_FISHERMAN,
"TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE,
"TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE,
+ "TRAIT_SILENT_REACTIONS" = TRAIT_SILENT_REACTIONS,
),
/mob = list(
"TRAIT_ABDUCTOR_SCIENTIST_TRAINING" = TRAIT_ABDUCTOR_SCIENTIST_TRAINING,
@@ -121,6 +122,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_GIANT" = TRAIT_GIANT,
"TRAIT_GODMODE" = TRAIT_GODMODE,
"TRAIT_GOOD_HEARING" = TRAIT_GOOD_HEARING,
+ "TRAIT_GRABRESISTANCE" = TRAIT_GRABRESISTANCE,
"TRAIT_GRABWEAKNESS" = TRAIT_GRABWEAKNESS,
"TRAIT_GREENTEXT_CURSED" = TRAIT_GREENTEXT_CURSED,
"TRAIT_GUNFLIP" = TRAIT_GUNFLIP,
@@ -262,6 +264,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE,
"TRAIT_SKITTISH" = TRAIT_SKITTISH,
"TRAIT_SLEEPIMMUNE" = TRAIT_SLEEPIMMUNE,
+ "TRAIT_SLIPPERY_WHEN_WET" = TRAIT_SLIPPERY_WHEN_WET,
"TRAIT_SMOKER" = TRAIT_SMOKER,
"TRAIT_SNOB" = TRAIT_SNOB,
"TRAIT_SOFTSPOKEN" = TRAIT_SOFTSPOKEN,
@@ -283,6 +286,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_TAGGER" = TRAIT_TAGGER,
"TRAIT_TENTACLE_IMMUNE" = TRAIT_TENTACLE_IMMUNE,
"TRAIT_TESLA_SHOCKIMMUNE" = TRAIT_TESLA_SHOCKIMMUNE,
+ "TRAIT_TETRODOTOXIN_HEALING" = TRAIT_TETRODOTOXIN_HEALING,
"TRAIT_THERMAL_VISION" = TRAIT_THERMAL_VISION,
"TRAIT_THROWINGARM" = TRAIT_THROWINGARM,
"TRAIT_TIME_STOP_IMMUNE" = TRAIT_TIME_STOP_IMMUNE,
@@ -308,11 +312,14 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_VIRUSIMMUNE" = TRAIT_VIRUSIMMUNE,
"TRAIT_VIRUS_RESISTANCE" = TRAIT_VIRUS_RESISTANCE,
"TRAIT_VORACIOUS" = TRAIT_VORACIOUS,
- "TRAIT_WOUND_LICKER" = TRAIT_WOUND_LICKER,
+ "TRAIT_WATER_ADAPTATION" = TRAIT_WATER_ADAPTATION,
+ "TRAIT_WATER_HATER" = TRAIT_WATER_HATER,
"TRAIT_WEAK_SOUL" = TRAIT_WEAK_SOUL,
"TRAIT_WEB_SURFER" = TRAIT_WEB_SURFER,
"TRAIT_WEB_WEAVER" = TRAIT_WEB_WEAVER,
+ "TRAIT_WET_FOR_LONGER" = TRAIT_WET_FOR_LONGER,
"TRAIT_WINE_TASTER" = TRAIT_WINE_TASTER,
+ "TRAIT_WOUND_LICKER" = TRAIT_WOUND_LICKER,
"TRAIT_XENO_HOST" = TRAIT_XENO_HOST,
"TRAIT_XENO_IMMUNE" = TRAIT_XENO_IMMUNE,
"TRAIT_XRAY_HEARING" = TRAIT_XRAY_HEARING,
@@ -343,14 +350,17 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_FISH_FED_LUBE" = TRAIT_FISH_FED_LUBE,
"TRAIT_FISH_FROM_CASE" = TRAIT_FISH_FROM_CASE,
"TRAIT_FISH_INK_ON_COOLDOWN" = TRAIT_FISH_INK_ON_COOLDOWN,
+ "TRAIT_FISH_MUTAGENIC" = TRAIT_FISH_MUTAGENIC,
"TRAIT_FISH_NO_HUNGER" = TRAIT_FISH_NO_HUNGER,
"TRAIT_FISH_NO_MATING" = TRAIT_FISH_NO_MATING,
+ "TRAIT_FISH_ON_TESLIUM" = TRAIT_FISH_ON_TESLIUM,
"TRAIT_FISH_RECESSIVE" = TRAIT_FISH_RECESSIVE,
"TRAIT_FISH_SELF_REPRODUCE" = TRAIT_FISH_SELF_REPRODUCE,
"TRAIT_FISH_SHOULD_TWOHANDED" = TRAIT_FISH_SHOULD_TWOHANDED,
"TRAIT_FISH_STASIS" = TRAIT_FISH_STASIS,
"TRAIT_FISH_STINGER" = TRAIT_FISH_STINGER,
"TRAIT_FISH_SURVIVE_COOKING" = TRAIT_FISH_SURVIVE_COOKING,
+ "TRAIT_FISH_QUICK_GROWTH" = TRAIT_FISH_QUICK_GROWTH,
"TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE,
"TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY,
"TRAIT_YUCKY_FISH" = TRAIT_YUCKY_FISH,
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 5e4ee1e849dbb..f9191348fdb24 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -183,6 +183,11 @@
desc = "There's sleeping gas in the air and you're breathing it in. Find some fresh air. The box in your backpack has an oxygen tank and breath mask in it."
icon_state = ALERT_TOO_MUCH_N2O
+/atom/movable/screen/alert/not_enough_water
+ name = "Choking (No H2O)"
+ desc = "You're not getting enough water. Drench yourself in some water (e.g. showers) or get some water vapor before you pass out!"
+ icon_state = ALERT_NOT_ENOUGH_WATER
+
//End gas alerts
/atom/movable/screen/alert/gross
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index 83ac1b8be93ed..91b5c9b8e2af8 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -72,6 +72,8 @@
if(screen.needs_offsetting)
screen.plane = GET_NEW_PLANE(initial(screen.plane), offset)
+INITIALIZE_IMMEDIATE(/atom/movable/screen/fullscreen)
+
/atom/movable/screen/fullscreen
icon = 'icons/hud/screen_full.dmi'
icon_state = "default"
diff --git a/code/controllers/subsystem/ai_controllers.dm b/code/controllers/subsystem/ai_controllers.dm
index 49c571a9a0763..8a5eb43bfc9dd 100644
--- a/code/controllers/subsystem/ai_controllers.dm
+++ b/code/controllers/subsystem/ai_controllers.dm
@@ -8,8 +8,10 @@ SUBSYSTEM_DEF(ai_controllers)
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
///type of status we are interested in running
var/planning_status = AI_STATUS_ON
- /// The tick cost of all active AI, calculated on fire.
+ /// The average tick cost of all active AI, calculated on fire.
var/our_cost
+ /// The tick cost of all currently processed AI, being summed together
+ var/summing_cost
/datum/controller/subsystem/ai_controllers/Initialize()
setup_subtrees()
@@ -21,6 +23,8 @@ SUBSYSTEM_DEF(ai_controllers)
return ..()
/datum/controller/subsystem/ai_controllers/fire(resumed)
+ if(!resumed)
+ summing_cost = 0
var/timer = TICK_USAGE_REAL
for(var/datum/ai_controller/ai_controller as anything in GLOB.ai_controllers_by_status[planning_status])
if(!ai_controller.able_to_plan)
@@ -30,7 +34,14 @@ SUBSYSTEM_DEF(ai_controllers)
if(!length(ai_controller.current_behaviors)) //Still no plan
ai_controller.planning_failed()
- our_cost = MC_AVERAGE(our_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ if(MC_TICK_CHECK)
+ break
+
+ summing_cost += TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)
+ if(MC_TICK_CHECK)
+ return
+
+ our_cost = MC_AVERAGE(our_cost, summing_cost)
///Creates all instances of ai_subtrees and assigns them to the ai_subtrees list.
/datum/controller/subsystem/ai_controllers/proc/setup_subtrees()
diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm
index 45354d4bd6164..aca5ca557cdc8 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -26,6 +26,7 @@ GLOBAL_LIST_INIT_TYPED(quirk_blacklist, /list/datum/quirk, list(
list(/datum/quirk/photophobia, /datum/quirk/nyctophobia),
list(/datum/quirk/item_quirk/settler, /datum/quirk/freerunning),
list(/datum/quirk/numb, /datum/quirk/selfaware),
+ list(/datum/quirk/empath, /datum/quirk/evil),
))
GLOBAL_LIST_INIT(quirk_string_blacklist, generate_quirk_string_blacklist())
diff --git a/code/controllers/subsystem/sprite_accessories.dm b/code/controllers/subsystem/sprite_accessories.dm
index c59e8d89a0e1f..2d121daa7a0a5 100644
--- a/code/controllers/subsystem/sprite_accessories.dm
+++ b/code/controllers/subsystem/sprite_accessories.dm
@@ -43,9 +43,10 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity
var/list/tail_spines_list
//Mutant Human bits
- var/list/tails_list_human
+ var/list/tails_list_felinid
var/list/tails_list_lizard
var/list/tails_list_monkey
+ var/list/tails_list_fish
var/list/ears_list
var/list/wings_list
var/list/wings_open_list
@@ -87,9 +88,11 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity
socks_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/socks)[DEFAULT_SPRITE_LIST]
lizard_markings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/lizard_markings, add_blank = TRUE)[DEFAULT_SPRITE_LIST]
- tails_list_human = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, add_blank = TRUE)[DEFAULT_SPRITE_LIST]
+ tails_list_felinid = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/felinid, add_blank = TRUE)[DEFAULT_SPRITE_LIST]
tails_list_lizard = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard)[DEFAULT_SPRITE_LIST]
tails_list_monkey = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/monkey)[DEFAULT_SPRITE_LIST]
+ //tails fo fish organ infusions, not for prefs.
+ tails_list_fish = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/fish)[DEFAULT_SPRITE_LIST]
snouts_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts)[DEFAULT_SPRITE_LIST]
horns_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, add_blank = TRUE)[DEFAULT_SPRITE_LIST]
ears_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, add_blank = TRUE)[DEFAULT_SPRITE_LIST]
diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm
index 76e87bc14f3cd..d6230ec8d3534 100644
--- a/code/datums/ai/_ai_controller.dm
+++ b/code/datums/ai/_ai_controller.dm
@@ -90,8 +90,17 @@ multiple modular subtrees with behaviors
///Sets the current movement target, with an optional param to override the movement behavior
/datum/ai_controller/proc/set_movement_target(source, atom/target, datum/ai_movement/new_movement)
+ if(current_movement_target)
+ UnregisterSignal(current_movement_target, list(COMSIG_MOVABLE_MOVED, COMSIG_PREQDELETED))
+ if(!isnull(target) && !isatom(target))
+ stack_trace("[pawn]'s current movement target is not an atom, rather a [target.type]! Did you accidentally set it to a weakref?")
+ CancelActions()
+ return
movement_target_source = source
current_movement_target = target
+ if(!isnull(current_movement_target))
+ RegisterSignal(current_movement_target, COMSIG_MOVABLE_MOVED, PROC_REF(on_movement_target_move))
+ RegisterSignal(current_movement_target, COMSIG_PREQDELETED, PROC_REF(on_movement_target_delete))
if(new_movement)
change_ai_movement_type(new_movement)
@@ -153,6 +162,20 @@ multiple modular subtrees with behaviors
SIGNAL_HANDLER
set_new_cells()
+ if(current_movement_target)
+ check_target_max_distance()
+
+/datum/ai_controller/proc/on_movement_target_move(atom/source)
+ SIGNAL_HANDLER
+ check_target_max_distance()
+
+/datum/ai_controller/proc/on_movement_target_delete(atom/source)
+ SIGNAL_HANDLER
+ set_movement_target(source = type, target = null)
+
+/datum/ai_controller/proc/check_target_max_distance()
+ if(get_dist(current_movement_target, pawn) > max_target_distance)
+ CancelActions()
/datum/ai_controller/proc/set_new_cells()
if(isnull(our_cells))
@@ -341,16 +364,6 @@ multiple modular subtrees with behaviors
///Runs any actions that are currently running
/datum/ai_controller/process(seconds_per_tick)
- if(current_movement_target)
- if(!isatom(current_movement_target))
- stack_trace("[pawn]'s current movement target is not an atom, rather a [current_movement_target.type]! Did you accidentally set it to a weakref?")
- CancelActions()
- return
-
- if(get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range
- CancelActions()
- return
-
for(var/datum/ai_behavior/current_behavior as anything in current_behaviors)
// Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior
@@ -364,9 +377,9 @@ multiple modular subtrees with behaviors
ProcessBehavior(action_seconds_per_tick, current_behavior)
return
- if(!current_movement_target)
- stack_trace("[pawn] wants to perform action type [current_behavior.type] which requires movement, but has no current movement target!")
- return //This can cause issues, so don't let these slide.
+ if(isnull(current_movement_target))
+ fail_behavior(current_behavior)
+ return
///Stops pawns from performing such actions that should require the target to be adjacent.
var/atom/movable/moving_pawn = pawn
var/can_reach = !(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_REACH) || moving_pawn.CanReach(current_movement_target)
@@ -532,11 +545,14 @@ multiple modular subtrees with behaviors
if(!length(current_behaviors))
return
for(var/datum/ai_behavior/current_behavior as anything in current_behaviors)
- var/list/arguments = list(src, FALSE)
- var/list/stored_arguments = behavior_args[current_behavior.type]
- if(stored_arguments)
- arguments += stored_arguments
- current_behavior.finish_action(arglist(arguments))
+ fail_behavior(current_behavior)
+
+/datum/ai_controller/proc/fail_behavior(datum/ai_behavior/current_behavior)
+ var/list/arguments = list(src, FALSE)
+ var/list/stored_arguments = behavior_args[current_behavior.type]
+ if(stored_arguments)
+ arguments += stored_arguments
+ current_behavior.finish_action(arglist(arguments))
/// Turn the controller on or off based on if you're alive, we only register to this if the flag is present so don't need to check again
/datum/ai_controller/proc/on_stat_changed(mob/living/source, new_stat)
diff --git a/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm b/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm
index d0250cce686eb..5b9101273bcbc 100644
--- a/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm
+++ b/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm
@@ -127,15 +127,21 @@
if(ORGAN_COLOR_INHERIT)
draw_color = bodypart_owner.draw_color
if(ORGAN_COLOR_HAIR)
+ var/datum/species/species = bodypart_owner.owner?.dna?.species
+ var/fixed_color = species?.get_fixed_hair_color(bodypart_owner)
if(!ishuman(bodypart_owner.owner))
+ draw_color = fixed_color
return
var/mob/living/carbon/human/human_owner = bodypart_owner.owner
var/obj/item/bodypart/head/my_head = human_owner.get_bodypart(BODY_ZONE_HEAD) //not always the same as bodypart_owner
//head hair color takes priority, owner hair color is a backup if we lack a head or something
- if(my_head)
- draw_color = my_head.hair_color
- else
- draw_color = human_owner.hair_color
+ if(!my_head)
+ draw_color = fixed_color || human_owner.hair_color
+ return
+ if(my_head.head_flags & (HEAD_HAIR|HEAD_FACIAL_HAIR))
+ draw_color = my_head.fixed_hair_color || my_head.hair_color
+ else //inherit mutant color of the bodypart if the owner doesn't have hair.
+ draw_color = bodypart_owner.draw_color
return TRUE
diff --git a/code/datums/bodypart_overlays/texture_bodypart_overlay.dm b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm
index 83c8ce5f12121..623a61b8912f0 100644
--- a/code/datums/bodypart_overlays/texture_bodypart_overlay.dm
+++ b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm
@@ -22,3 +22,7 @@
blocks_emissive = EMISSIVE_BLOCK_NONE
texture_icon_state = "spacey"
texture_icon = 'icons/mob/human/textures.dmi'
+
+/datum/bodypart_overlay/texture/carpskin
+ texture_icon_state = "carpskin"
+ texture_icon = 'icons/mob/human/textures.dmi'
diff --git a/code/datums/components/bubble_icon_override.dm b/code/datums/components/bubble_icon_override.dm
new file mode 100644
index 0000000000000..da070b8f5dc82
--- /dev/null
+++ b/code/datums/components/bubble_icon_override.dm
@@ -0,0 +1,99 @@
+/**
+ * A component that overrides the bubble_icon variable when equipped or implanted
+ * while having a simple priority system, so accessories have higher priority than
+ * organs, for example.
+ */
+/datum/component/bubble_icon_override
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ can_transfer = TRUE //sure why not
+ ///The override to the default bubble icon for the atom
+ var/bubble_icon
+ ///The priority of this bubble icon compared to others
+ var/priority
+
+/datum/component/bubble_icon_override/Initialize(bubble_icon, priority)
+ if(!isclothing(parent) && !isorgan(parent))
+ return COMPONENT_INCOMPATIBLE
+ src.bubble_icon = bubble_icon
+ src.priority = priority
+
+/datum/component/bubble_icon_override/RegisterWithParent()
+ if(isclothing(parent))
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equipped))
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_dropped))
+ else if(isorgan(parent))
+ RegisterSignal(parent, COMSIG_ORGAN_IMPLANTED, PROC_REF(on_organ_implanted))
+ RegisterSignal(parent, COMSIG_ORGAN_REMOVED, PROC_REF(on_organ_removed))
+ var/mob/living/target = get_bubble_icon_target()
+ if(target)
+ register_owner(target)
+
+/datum/component/bubble_icon_override/proc/register_owner(mob/living/owner)
+ RegisterSignal(owner, COMSIG_GET_BUBBLE_ICON, PROC_REF(return_bubble_icon))
+ get_bubble_icon(owner)
+
+/datum/component/bubble_icon_override/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_ITEM_EQUIPPED,
+ COMSIG_ITEM_DROPPED,
+ COMSIG_ORGAN_IMPLANTED,
+ COMSIG_ORGAN_REMOVED,
+ ))
+ var/mob/living/target = get_bubble_icon_target()
+ if(target)
+ unregister_owner(target)
+
+/datum/component/bubble_icon_override/proc/unregister_owner(mob/living/owner)
+ UnregisterSignal(owner, list(COMSIG_GET_BUBBLE_ICON))
+ get_bubble_icon(owner)
+
+///Returns the potential wearer/owner of the object when the component is un/registered to/from it
+/datum/component/bubble_icon_override/proc/get_bubble_icon_target()
+ if(isclothing(parent))
+ var/obj/item/clothing/clothing = parent
+ if(istype(clothing, /obj/item/clothing/accessory))
+ clothing = clothing.loc
+ if(!istype(clothing))
+ return null
+ var/mob/living/wearer = clothing.loc
+ if(istype(wearer) && (wearer.get_slot_by_item(clothing) & clothing.slot_flags))
+ return parent
+ else if(isorgan(parent))
+ var/obj/item/organ/organ = parent
+ return organ.owner
+
+/datum/component/bubble_icon_override/proc/on_equipped(obj/item/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+ if(slot & source.slot_flags)
+ register_owner(equipper)
+
+/datum/component/bubble_icon_override/proc/on_dropped(obj/item/source, mob/dropper)
+ SIGNAL_HANDLER
+ unregister_owner(dropper)
+
+/datum/component/bubble_icon_override/proc/on_organ_implanted(obj/item/organ/source, mob/owner)
+ SIGNAL_HANDLER
+ register_owner(owner)
+
+/datum/component/bubble_icon_override/proc/on_organ_removed(obj/item/organ/source, mob/owner)
+ SIGNAL_HANDLER
+ unregister_owner(owner)
+
+/**
+ * Get the bubble icon with the highest priority from all instances of bubble_icon_override
+ * currently registered with the target.
+ */
+/datum/component/bubble_icon_override/proc/get_bubble_icon(mob/living/target)
+ if(QDELETED(parent))
+ return
+ var/list/holder = list(null)
+ SEND_SIGNAL(target, COMSIG_GET_BUBBLE_ICON, holder)
+ var/bubble_icon = holder[1]
+ target.bubble_icon = bubble_icon || initial(target.bubble_icon)
+
+/datum/component/bubble_icon_override/proc/return_bubble_icon(datum/source, list/holder)
+ SIGNAL_HANDLER
+ var/enemy_priority = holder[holder[1]]
+ if(enemy_priority < priority)
+ holder[1] = bubble_icon
+ holder[bubble_icon] = priority
diff --git a/code/datums/components/fish_growth.dm b/code/datums/components/fish_growth.dm
index 3ec1427fd51a8..7c9aed1048c27 100644
--- a/code/datums/components/fish_growth.dm
+++ b/code/datums/components/fish_growth.dm
@@ -44,7 +44,6 @@
return
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[result_type]
evolution.RegisterSignal(parent, COMSIG_FISH_BEFORE_GROWING, TYPE_PROC_REF(/datum/fish_evolution, growth_checks))
- evolution.register_fish(parent)
/datum/component/fish_growth/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_FISH_LIFE, COMSIG_FISH_BEFORE_GROWING))
@@ -55,6 +54,8 @@
return
var/deciseconds_elapsed = seconds_per_tick * 10
var/growth = growth_rate * deciseconds_elapsed
+ if(HAS_TRAIT(source, TRAIT_FISH_QUICK_GROWTH))
+ growth *= 2
if(SEND_SIGNAL(source, COMSIG_FISH_BEFORE_GROWING, seconds_per_tick, growth) & COMPONENT_DONT_GROW)
return
maturation += growth
diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm
index 9e2964273fd48..08002615ad01c 100644
--- a/code/datums/components/food/edible.dm
+++ b/code/datums/components/food/edible.dm
@@ -599,8 +599,12 @@ Behavior that's still missing from this component that original food items had t
return food.crafting_complexity + complexity_to_add
/// Get food quality adjusted according to eater's preferences
-/datum/component/edible/proc/get_perceived_food_quality(mob/living/carbon/human/eater)
+/datum/component/edible/proc/get_perceived_food_quality(mob/living/eater)
var/food_quality = get_recipe_complexity()
+ var/list/extra_quality = list()
+ SEND_SIGNAL(eater, COMSIG_LIVING_GET_PERCEIVED_FOOD_QUALITY, src, extra_quality)
+ for(var/quality in extra_quality)
+ food_quality += quality
if(HAS_TRAIT(parent, TRAIT_FOOD_SILVER)) // it's not real food
if(!isjellyperson(eater)) //if you aren't a jellyperson, it makes you sick no matter how nice it looks
diff --git a/code/datums/components/holderloving.dm b/code/datums/components/holderloving.dm
index 0670fa6086e2c..e41d986600df6 100644
--- a/code/datums/components/holderloving.dm
+++ b/code/datums/components/holderloving.dm
@@ -39,6 +39,7 @@
COMSIG_ATOM_EXITED,
COMSIG_ITEM_STORED,
), PROC_REF(check_my_loc))
+ RegisterSignal(parent, COMSIG_ITEM_PRE_UNEQUIP, PROC_REF(no_unequip))
/datum/component/holderloving/UnregisterFromParent()
UnregisterSignal(holder, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
@@ -48,6 +49,7 @@
COMSIG_ATOM_ENTERED,
COMSIG_ATOM_EXITED,
COMSIG_ITEM_STORED,
+ COMSIG_ITEM_PRE_UNEQUIP,
))
/datum/component/holderloving/PostTransfer()
@@ -63,6 +65,7 @@
/datum/component/holderloving/proc/holder_deleting(datum/source, force)
SIGNAL_HANDLER
+
if(del_parent_with_holder)
qdel(parent)
else
@@ -70,6 +73,20 @@
/datum/component/holderloving/proc/check_my_loc(datum/source)
SIGNAL_HANDLER
+
var/obj/item/item_parent = parent
if(!check_valid_loc(item_parent.loc))
item_parent.forceMove(holder)
+
+/datum/component/holderloving/proc/no_unequip(obj/item/I, force, atom/newloc, no_move, invdrop, silent)
+ SIGNAL_HANDLER
+
+ // just allow it
+ if(force)
+ return NONE
+ // dropping onto a turf just forcemoves it back to the holder. let it happen, it's intuitive
+ // no_move says it's just going to be moved a second time. so let it happen, it'll just be moved back if it's invalid anyway
+ if(isturf(newloc) || no_move)
+ return NONE
+ // the item is being unequipped to somewhere invalid. stop it
+ return COMPONENT_ITEM_BLOCK_UNEQUIP
diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm
index 8a934cdd4c1d5..8d2dbda379eda 100644
--- a/code/datums/components/slippery.dm
+++ b/code/datums/components/slippery.dm
@@ -38,14 +38,11 @@
COMSIG_ATOM_ENTERED = PROC_REF(Slip),
)
- ///what we give to connect_loc if we're an item and get equipped by a mob. makes slippable mobs moving over our holder slip
- var/static/list/holder_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(Slip_on_wearer),
+ ///what we give to connect_loc if we're an item and get equipped by a mob, or if we're a mob. makes slippable mobs moving over the mob slip
+ var/static/list/mob_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(slip_on_mob),
)
- /// The connect_loc_behalf component for the holder_connections list.
- var/datum/weakref/holder_connect_loc_behalf
-
/**
* Initialize the slippery component behaviour
*
@@ -79,14 +76,14 @@
src.slot_whitelist = slot_whitelist
add_connect_loc_behalf_to_parent()
- if(ismovable(parent))
- if(isitem(parent))
- RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
- RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
- RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses))
- RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses))
- else
+ if(!ismovable(parent))
RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(Slip))
+ else if(isitem(parent))
+ src.lube_flags |= SLIPPERY_WHEN_LYING_DOWN
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
+ RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses))
+ RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses))
/datum/component/slippery/Destroy(force)
can_slip_callback = null
@@ -114,8 +111,13 @@
lube_flags = previous_lube_flags
/datum/component/slippery/proc/add_connect_loc_behalf_to_parent()
- if(ismovable(parent))
- AddComponent(/datum/component/connect_loc_behalf, parent, default_connections)
+ var/list/connections_to_use
+ if(isliving(parent))
+ connections_to_use = mob_connections
+ else if(ismovable(parent))
+ connections_to_use = default_connections
+ if(connections_to_use)
+ AddComponent(/datum/component/connect_loc_behalf, parent, connections_to_use)
/datum/component/slippery/InheritComponent(
datum/component/slippery/component,
@@ -184,7 +186,7 @@
if((!LAZYLEN(slot_whitelist) || (slot in slot_whitelist)) && isliving(equipper))
holder = equipper
qdel(GetComponent(/datum/component/connect_loc_behalf))
- AddComponent(/datum/component/connect_loc_behalf, holder, holder_connections)
+ AddComponent(/datum/component/connect_loc_behalf, holder, mob_connections)
RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(holder_deleted))
/**
@@ -227,10 +229,11 @@
* * source - the source of the signal
* * arrived - the atom/movable that slipped on us.
*/
-/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+/datum/component/slippery/proc/slip_on_mob(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
- if(holder.body_position == LYING_DOWN && !holder.buckled)
+ var/mob/living/living = holder || parent
+ if(!(lube_flags & SLIPPERY_WHEN_LYING_DOWN) || (living.body_position == LYING_DOWN && !living.buckled))
Slip(source, arrived)
/datum/component/slippery/UnregisterFromParent()
diff --git a/code/datums/components/splat.dm b/code/datums/components/splat.dm
index 5d47d17b98c9c..d22613204bbbd 100644
--- a/code/datums/components/splat.dm
+++ b/code/datums/components/splat.dm
@@ -71,4 +71,5 @@
if(can_splat_on && is_type_in_typecache(hit_atom, GLOB.splattable))
hit_atom.AddComponent(/datum/component/face_decal/splat, icon_state, layer, splat_color || source.color, memory_type, moodlet_type)
SEND_SIGNAL(source, COMSIG_MOVABLE_SPLAT, hit_atom)
- qdel(source)
+ if(!isprojectile(source))
+ qdel(source)
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index df3fbd2654d77..3ccbc7b71ea25 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -223,7 +223,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
if(features["lizard_markings"])
L[DNA_LIZARD_MARKINGS_BLOCK] = construct_block(SSaccessories.lizard_markings_list.Find(features["lizard_markings"]), length(SSaccessories.lizard_markings_list))
if(features["tail_cat"])
- L[DNA_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_human.Find(features["tail_cat"]), length(SSaccessories.tails_list_human))
+ L[DNA_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_felinid.Find(features["tail_cat"]), length(SSaccessories.tails_list_felinid))
if(features["tail_lizard"])
L[DNA_LIZARD_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_lizard.Find(features["tail_lizard"]), length(SSaccessories.tails_list_lizard))
if(features["snout"])
@@ -246,6 +246,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
L[DNA_MUSHROOM_CAPS_BLOCK] = construct_block(SSaccessories.caps_list.Find(features["caps"]), length(SSaccessories.caps_list))
if(features["pod_hair"])
L[DNA_POD_HAIR_BLOCK] = construct_block(SSaccessories.pod_hair_list.Find(features["pod_hair"]), length(SSaccessories.pod_hair_list))
+ if(features["fish_tail"])
+ L[DNA_FISH_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_fish.Find(features["fish_tail"]), length(SSaccessories.tails_list_fish))
for(var/blocknum in 1 to DNA_FEATURE_BLOCKS)
. += L[blocknum] || random_string(GET_UI_BLOCK_LEN(blocknum), GLOB.hex_characters)
@@ -367,7 +369,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
if(DNA_LIZARD_MARKINGS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.lizard_markings_list.Find(features["lizard_markings"]), length(SSaccessories.lizard_markings_list)))
if(DNA_TAIL_BLOCK)
- set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_human.Find(features["tail_cat"]), length(SSaccessories.tails_list_human)))
+ set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_felinid.Find(features["tail_cat"]), length(SSaccessories.tails_list_felinid)))
if(DNA_LIZARD_TAIL_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_lizard.Find(features["tail_lizard"]), length(SSaccessories.tails_list_lizard)))
if(DNA_SNOUT_BLOCK)
@@ -390,6 +392,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
set_uni_feature_block(blocknumber, construct_block(SSaccessories.caps_list.Find(features["caps"]), length(SSaccessories.caps_list)))
if(DNA_POD_HAIR_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.pod_hair_list.Find(features["pod_hair"]), length(SSaccessories.pod_hair_list)))
+ if(DNA_FISH_TAIL_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_fish.Find(features["fish_tail"]), length(SSaccessories.tails_list_fish)))
//Please use add_mutation or activate_mutation instead
/datum/dna/proc/force_give(datum/mutation/human/human_mutation)
@@ -672,7 +676,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
if(dna.features["spines"])
dna.features["spines"] = SSaccessories.spines_list[deconstruct_block(get_uni_feature_block(features, DNA_SPINES_BLOCK), length(SSaccessories.spines_list))]
if(dna.features["tail_cat"])
- dna.features["tail_cat"] = SSaccessories.tails_list_human[deconstruct_block(get_uni_feature_block(features, DNA_TAIL_BLOCK), length(SSaccessories.tails_list_human))]
+ dna.features["tail_cat"] = SSaccessories.tails_list_felinid[deconstruct_block(get_uni_feature_block(features, DNA_TAIL_BLOCK), length(SSaccessories.tails_list_felinid))]
if(dna.features["tail_lizard"])
dna.features["tail_lizard"] = SSaccessories.tails_list_lizard[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_TAIL_BLOCK), length(SSaccessories.tails_list_lizard))]
if(dna.features["ears"])
@@ -691,6 +695,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
dna.features["caps"] = SSaccessories.caps_list[deconstruct_block(get_uni_feature_block(features, DNA_MUSHROOM_CAPS_BLOCK), length(SSaccessories.caps_list))]
if(dna.features["pod_hair"])
dna.features["pod_hair"] = SSaccessories.pod_hair_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_HAIR_BLOCK), length(SSaccessories.pod_hair_list))]
+ if(dna.features["fish_tail"])
+ dna.features["fish_tail"] = SSaccessories.tails_list_fish[deconstruct_block(get_uni_feature_block(features, DNA_FISH_TAIL_BLOCK), length(SSaccessories.tails_list_fish))]
for(var/obj/item/organ/organ in organs)
organ.mutate_feature(features, src)
diff --git a/code/datums/drift_handler.dm b/code/datums/drift_handler.dm
index 27a4fd4bc9154..7000483f9ab11 100644
--- a/code/datums/drift_handler.dm
+++ b/code/datums/drift_handler.dm
@@ -213,6 +213,10 @@
if (drift_force < INERTIA_FORCE_SPACEMOVE_GRAB || isnull(drifting_loop))
return
+ if (!isnull(source.client) && source.client.intended_direction)
+ if ((source.client.intended_direction & movement_dir) && !(get_dir(source, backup) & movement_dir))
+ return
+
if (drift_force <= INERTIA_FORCE_SPACEMOVE_REDUCTION / source.inertia_force_weight)
glide_to_halt(get_loop_delay(source))
return COMPONENT_PREVENT_SPACEMOVE_HALT
diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm
index 9b4dfddfbd1fb..698f7896a70b4 100644
--- a/code/datums/elements/footstep.dm
+++ b/code/datums/elements/footstep.dm
@@ -71,7 +71,10 @@
if(source.body_position == LYING_DOWN) //play crawling sound if we're lying
if(turf.footstep)
- playsound(turf, 'sound/effects/footstep/crawl1.ogg', 15 * volume, falloff_distance = 1, vary = sound_vary)
+ var/sound = 'sound/effects/footstep/crawl1.ogg'
+ if(HAS_TRAIT(source, TRAIT_FLOPPING))
+ sound = pick(SFX_FISH_PICKUP, 'sound/mobs/non-humanoids/fish/fish_drop1.ogg')
+ playsound(turf, sound, 15 * volume, falloff_distance = 1, vary = sound_vary)
return
if(iscarbon(source) && source.move_intent == MOVE_INTENT_WALK)
diff --git a/code/datums/elements/organ_set_bonus.dm b/code/datums/elements/organ_set_bonus.dm
index aeb63356fb485..1c75bf7de1486 100644
--- a/code/datums/elements/organ_set_bonus.dm
+++ b/code/datums/elements/organ_set_bonus.dm
@@ -57,6 +57,8 @@
var/required_biotype = MOB_ORGANIC
/// A list of traits added to the mob upon bonus activation, can be of any length.
var/list/bonus_traits = list()
+ /// Limb overlay to apply upon activation
+ var/limb_overlay
/datum/status_effect/organ_set_bonus/proc/set_organs(new_value)
organs = new_value
@@ -80,6 +82,13 @@
owner.add_traits(bonus_traits, REF(src))
if(bonus_activate_text)
to_chat(owner, bonus_activate_text)
+ if(!iscarbon(owner) || !limb_overlay)
+ return TRUE
+ var/mob/living/carbon/carbon_owner = owner
+ for(var/obj/item/bodypart/limb in carbon_owner.bodyparts)
+ limb.add_bodypart_overlay(new limb_overlay())
+ limb.variable_color = COLOR_WHITE
+ carbon_owner.update_body()
return TRUE
/datum/status_effect/organ_set_bonus/proc/disable_bonus()
@@ -89,3 +98,12 @@
owner.remove_traits(bonus_traits, REF(src))
if(bonus_deactivate_text)
to_chat(owner, bonus_deactivate_text)
+ if(!iscarbon(owner) || QDELETED(owner) || !limb_overlay)
+ return
+ var/mob/living/carbon/carbon_owner = owner
+ for(var/obj/item/bodypart/limb in carbon_owner.bodyparts)
+ var/overlay = locate(limb_overlay) in limb.bodypart_overlays
+ if(overlay)
+ limb.remove_bodypart_overlay(overlay)
+ limb.variable_color = null
+ carbon_owner.update_body()
diff --git a/code/datums/elements/venomous.dm b/code/datums/elements/venomous.dm
index 9f9e4940df13e..93bb455509821 100644
--- a/code/datums/elements/venomous.dm
+++ b/code/datums/elements/venomous.dm
@@ -7,7 +7,7 @@
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
///Path of the reagent added
- var/poison_type
+ var/reagents
///Details of how we inject our venom
var/injection_flags
///How much of the reagent added. if it's a list, it'll pick a range with the range being list(lower_value, upper_value)
@@ -17,7 +17,7 @@
/datum/element/venomous/Attach(datum/target, poison_type, amount_added, injection_flags = NONE, thrown_effect = FALSE)
. = ..()
- src.poison_type = poison_type
+ src.reagents = poison_type
src.amount_added = amount_added
src.injection_flags = injection_flags
src.thrown_effect = thrown_effect
@@ -41,4 +41,17 @@
final_amount_added = rand(amount_added[1], amount_added[2])
else
final_amount_added = amount_added
- target.reagents?.add_reagent(poison_type, final_amount_added)
+
+ var/datum/reagents/tmp_holder = new(final_amount_added)
+ tmp_holder.my_atom = src
+ tmp_holder.add_reagent(reagents, final_amount_added)
+
+ tmp_holder.trans_to(
+ target = target,
+ amount = tmp_holder.total_volume,
+ multiplier = 1,
+ methods = INJECT,
+ transferred_by = ismob(element_owner) ? element_owner : null,
+ show_message = FALSE,
+ )
+ qdel(tmp_holder)
diff --git a/code/datums/elements/waddling.dm b/code/datums/elements/waddling.dm
index 45c7fe5e93773..d89504dbcfecb 100644
--- a/code/datums/elements/waddling.dm
+++ b/code/datums/elements/waddling.dm
@@ -18,7 +18,7 @@
return
if(isliving(moved))
var/mob/living/living_moved = moved
- if (living_moved.incapacitated || living_moved.body_position == LYING_DOWN)
+ if (living_moved.incapacitated || (living_moved.body_position == LYING_DOWN && !HAS_TRAIT(living_moved, TRAIT_FLOPPING)))
return
waddling_animation(moved)
diff --git a/code/datums/greyscale/config_types/mutant_organ_config.dm b/code/datums/greyscale/config_types/mutant_organ_config.dm
index 18789a27ccaaf..3427622472835 100644
--- a/code/datums/greyscale/config_types/mutant_organ_config.dm
+++ b/code/datums/greyscale/config_types/mutant_organ_config.dm
@@ -2,3 +2,8 @@
name = "Mutant Organ"
icon_file = 'icons/obj/medical/organs/infuser_organs.dmi'
json_config = 'code/datums/greyscale/json_configs/mutant_organs.json'
+
+/datum/greyscale_config/fish_tail
+ name = "Fish Tail"
+ icon_file = 'icons/obj/medical/organs/infuser_organs.dmi'
+ json_config = 'code/datums/greyscale/json_configs/fish_tail.json'
diff --git a/code/datums/greyscale/json_configs/fish_tail.json b/code/datums/greyscale/json_configs/fish_tail.json
new file mode 100644
index 0000000000000..3293c1df4ba08
--- /dev/null
+++ b/code/datums/greyscale/json_configs/fish_tail.json
@@ -0,0 +1,15 @@
+{
+ "fish_tail": [
+ {
+ "type": "icon_state",
+ "icon_state": "fish_tail",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "fish_tail_meat",
+ "blend_mode": "overlay"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code/datums/mood_events/dna_infuser_events.dm b/code/datums/mood_events/dna_infuser_events.dm
index 6da7235cfc1da..26c07d76b111e 100644
--- a/code/datums/mood_events/dna_infuser_events.dm
+++ b/code/datums/mood_events/dna_infuser_events.dm
@@ -7,3 +7,11 @@
description = "There's a lot that could be on your mind right now. But this feeling of contentedness, a universal calling to simply sit back and observe is washing over you..."
mood_change = 10
special_screen_obj = "mood_gondola"
+
+/datum/mood_event/fish_waterless
+ mood_change = -3
+ description = "It sucks to be dry. I feel like a fish out of water."
+
+/datum/mood_event/fish_water
+ mood_change = 1
+ description = "Glug glug!"
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 695bf43949653..30999a874b77b 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -501,3 +501,8 @@
// Felinids apparently hate being hit over the head with cardboard
if(isfelinid(owner))
mood_change = -2
+
+/datum/mood_event/encountered_evil
+ description = "I didn't want to believe it, but there are people out there that are genuinely evil."
+ mood_change = -4
+ timeout = 1 MINUTES
diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm
index 5684b20c454f7..7730ab16d9cc9 100644
--- a/code/datums/mutations/antenna.dm
+++ b/code/datums/mutations/antenna.dm
@@ -96,6 +96,11 @@
to_chat(owner, span_warning("You plunge into your mind... Yep, it's your mind."))
return
+ if(HAS_TRAIT(cast_on, TRAIT_EVIL))
+ to_chat(owner, span_warning("As you reach into [cast_on]'s mind, \
+ you feel the overwhelming emptiness within. A truly evil being. \
+ [HAS_TRAIT(owner, TRAIT_EVIL) ? "It's nice to find someone who is like-minded." : "What is wrong with this person?"]"))
+
to_chat(owner, span_boldnotice("You plunge into [cast_on]'s mind..."))
if(prob(20))
// chance to alert the read-ee
diff --git a/code/datums/quirks/neutral_quirks/evil.dm b/code/datums/quirks/neutral_quirks/evil.dm
new file mode 100644
index 0000000000000..6753a7d034cfd
--- /dev/null
+++ b/code/datums/quirks/neutral_quirks/evil.dm
@@ -0,0 +1,12 @@
+/datum/quirk/evil
+ name = "Fundamentally Evil"
+ desc = "Where you would have a soul is but an ink-black void. While you are committed to maintaining your social standing, \
+ anyone who stares too long into your cold, uncaring eyes will know the truth. You are truly evil. There is nothing \
+ wrong with you. You chose to be evil, committed to it. Your ambitions come first above all."
+ icon = FA_ICON_HAND_MIDDLE_FINGER
+ value = 0
+ mob_trait = TRAIT_EVIL
+ gain_text = span_notice("You shed what little remains of your humanity. You have work to do.")
+ lose_text = span_notice("You suddenly care more about others and their needs.")
+ medical_record_text = "Patient has passed all our social fitness tests with flying colours, but had trouble on the empathy tests."
+ mail_goodies = list(/obj/item/food/grown/citrus/lemon)
diff --git a/code/datums/sprite_accessories.dm b/code/datums/sprite_accessories.dm
index a4394b64d52e5..97a2c08644cfa 100644
--- a/code/datums/sprite_accessories.dm
+++ b/code/datums/sprite_accessories.dm
@@ -1744,6 +1744,23 @@
/// Describes which tail spine sprites to use, if any.
var/spine_key = NONE
+///Used for fish-infused tails, which come in different flavors.
+/datum/sprite_accessory/tails/fish
+ icon = 'icons/mob/human/fish_features.dmi'
+ color_src = HAIR_COLOR
+
+/datum/sprite_accessory/tails/fish/default
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/tails/fish/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/tails/fish/orca
+ name = "Orca"
+ icon_state = "orca"
+
/datum/sprite_accessory/tails/lizard
icon = 'icons/mob/human/species/lizard/lizard_tails.dmi'
spine_key = SPINE_KEY_LIZARD
@@ -1774,7 +1791,7 @@
icon_state = "short"
spine_key = NONE
-/datum/sprite_accessory/tails/human/cat
+/datum/sprite_accessory/tails/felinid/cat
name = "Cat"
icon = 'icons/mob/human/cat_features.dmi'
icon_state = "default"
diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm
index 8b5aadb3a06d6..b113589b70226 100644
--- a/code/datums/station_traits/negative_traits.dm
+++ b/code/datums/station_traits/negative_traits.dm
@@ -756,4 +756,13 @@
advisory_string += "The ongoing blizzard has interfered with our surveillance equipment, and we cannot provide an accurate threat summary at this time. We advise you to stay safe and avoid traversing the area around the station."
return advisory_string
+/datum/station_trait/spiked_drinks
+ name = "Spiked Drinks"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 3
+ cost = STATION_TRAIT_COST_LOW
+ show_in_report = TRUE
+ report_message = "Due to a mishap at the Robust Softdrinks Megafactory, some drinks may contain traces of ethanol or psychoactive chemicals."
+ trait_to_give = STATION_TRAIT_SPIKED_DRINKS
+
#undef GLOW_NEBULA
diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm
index ae7f73d4e0de0..a575d2619fe7d 100644
--- a/code/datums/status_effects/debuffs/fire_stacks.dm
+++ b/code/datums/status_effects/debuffs/fire_stacks.dm
@@ -300,12 +300,45 @@
enemy_types = list(/datum/status_effect/fire_handler/fire_stacks)
stack_modifier = -1
+ ///If the mob has the TRAIT_SLIPPERY_WHEN_WET trait, the mob gets this component while it's wet
+ var/datum/component/slippery/slipperiness
+
+/datum/status_effect/fire_handler/wet_stacks/on_apply()
+ . = ..()
+ RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_WET_FOR_LONGER), SIGNAL_REMOVETRAIT(TRAIT_WET_FOR_LONGER)), PROC_REF(update_wet_stack_modifier))
+ update_wet_stack_modifier()
+ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_SLIPPERY_WHEN_WET), PROC_REF(become_slippery))
+ RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_SLIPPERY_WHEN_WET), PROC_REF(no_longer_slippery))
+ if(HAS_TRAIT(owner, TRAIT_SLIPPERY_WHEN_WET))
+ become_slippery()
+ ADD_TRAIT(owner, TRAIT_IS_WET, TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/fire_handler/wet_stacks/on_remove()
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_IS_WET, TRAIT_STATUS_EFFECT(id))
+ if(HAS_TRAIT(owner, TRAIT_SLIPPERY_WHEN_WET))
+ no_longer_slippery()
+
+/datum/status_effect/fire_handler/wet_stacks/proc/update_wet_stack_modifier()
+ SIGNAL_HANDLER
+ stack_modifier = HAS_TRAIT(owner, TRAIT_WET_FOR_LONGER) ? -3.5 : -1
+
+/datum/status_effect/fire_handler/wet_stacks/proc/become_slippery()
+ SIGNAL_HANDLER
+ slipperiness = owner.AddComponent(/datum/component/slippery, 5 SECONDS, lube_flags = SLIPPERY_WHEN_LYING_DOWN)
+ ADD_TRAIT(owner, TRAIT_NO_SLIP_WATER, TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/fire_handler/wet_stacks/proc/no_longer_slippery()
+ SIGNAL_HANDLER
+ QDEL_NULL(slipperiness)
+ REMOVE_TRAIT(owner, TRAIT_NO_SLIP_WATER, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/fire_handler/wet_stacks/get_examine_text()
return "[owner.p_They()] look[owner.p_s()] a little soaked."
/datum/status_effect/fire_handler/wet_stacks/tick(seconds_between_ticks)
- adjust_stacks(-0.5 * seconds_between_ticks)
+ var/decay = HAS_TRAIT(owner, TRAIT_WET_FOR_LONGER) ? -0.035 : -0.5
+ adjust_stacks(decay * seconds_between_ticks)
if(stacks <= 0)
qdel(src)
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 3f4586d4d1ddd..8737cacede317 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -615,10 +615,10 @@
desc = "A good wash fills me with energy!"
icon_state = "shower_regen"
-/atom/movable/screen/alert/status_effect/shower_regen/catgirl
+/atom/movable/screen/alert/status_effect/shower_regen/hater
name = "Washing"
desc = "Waaater... Fuck this WATER!!"
- icon_state = "shower_regen_catgirl"
+ icon_state = "shower_regen_hater"
/datum/status_effect/shower_regen
id = "shower_regen"
@@ -630,11 +630,17 @@
/datum/status_effect/shower_regen/on_apply()
. = ..()
- if(isfelinid(owner))
- alert_type = /atom/movable/screen/alert/status_effect/shower_regen/catgirl
-
+ if(HAS_TRAIT(owner, TRAIT_WATER_HATER) && !HAS_TRAIT(owner, TRAIT_WATER_ADAPTATION))
+ alert_type = /atom/movable/screen/alert/status_effect/shower_regen/hater
/datum/status_effect/shower_regen/tick(seconds_between_ticks)
. = ..()
- var/heal_or_deal = isfelinid(owner) ? 1 : -1
+ var/water_adaptation = HAS_TRAIT(owner, TRAIT_WATER_ADAPTATION)
+ var/heal_or_deal = HAS_TRAIT(owner, TRAIT_WATER_HATER) && !water_adaptation ? 1 : -1
+ if(water_adaptation) //very mild healing for those with the water adaptation trait (fish infusion)
+ owner.adjustOxyLoss(-1 * seconds_between_ticks, updating_health = FALSE, required_biotype = MOB_ORGANIC)
+ owner.adjustFireLoss(-0.6 * seconds_between_ticks, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC)
+ owner.adjustToxLoss(-0.6 * seconds_between_ticks, updating_health = FALSE, required_biotype = MOB_ORGANIC)
+ owner.adjustBruteLoss(-0.6 * seconds_between_ticks, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC)
+ heal_or_deal *= 1.5
owner.adjustStaminaLoss(stamina_heal_per_tick * heal_or_deal * seconds_between_ticks)
diff --git a/code/datums/storage/subtypes/drone.dm b/code/datums/storage/subtypes/drone.dm
new file mode 100644
index 0000000000000..2df8f4c627966
--- /dev/null
+++ b/code/datums/storage/subtypes/drone.dm
@@ -0,0 +1,26 @@
+/datum/storage/drone
+ max_total_storage = 40
+ max_specific_storage = WEIGHT_CLASS_NORMAL
+ max_slots = 10
+ do_rustle = FALSE
+
+/datum/storage/drone/New(atom/parent, max_slots, max_specific_storage, max_total_storage)
+ . = ..()
+
+ var/static/list/drone_builtins = list(
+ /obj/item/crowbar/drone,
+ /obj/item/screwdriver/drone,
+ /obj/item/wrench/drone,
+ /obj/item/weldingtool/drone,
+ /obj/item/wirecutters/drone,
+ /obj/item/multitool/drone,
+ /obj/item/pipe_dispenser/drone,
+ /obj/item/t_scanner/drone,
+ /obj/item/analyzer/drone,
+ /obj/item/soap/drone,
+ )
+
+ set_holdable(drone_builtins)
+
+/datum/storage/drone/dump_content_at(atom/dest_object, dump_loc, mob/user)
+ return //no dumping of contents allowed
diff --git a/code/game/atom/atom_materials.dm b/code/game/atom/atom_materials.dm
index 31ac1992a9b64..cb88c83f3eb8c 100644
--- a/code/game/atom/atom_materials.dm
+++ b/code/game/atom/atom_materials.dm
@@ -64,14 +64,16 @@
return material_effects
/**
- * A proc that can be used to selectively control the statistics and affects from a material without affecting the others
+ * A proc that can be used to selectively control the stat changes and effects from a material without affecting the others.
+ *
* For example, we can have items made of two different materials, with the primary contributing a good 1.2 multiplier
* and the second a meager 0.3.
- * The GET_MATERIAL_MODIFIER macro will handles some modifiers where the minimum should be 1 if above 1 and the maximum
- * 1 if below 1, so you shouldn't worry about returning values between 0 and 1. Be ware about returning negative values tho.
+ *
+ * The GET_MATERIAL_MODIFIER macro will handles some modifications where the minimum should be 1 if above 1 and the maximum
+ * be 1 if below 1. Just don't return negative values.
*/
/atom/proc/get_material_multiplier(datum/material/custom_material, list/materials, index)
- return 1
+ return 1/length(materials)
///Called by apply_material_effects(). It ACTUALLY handles applying effects common to all atoms (depending on material flags)
/atom/proc/finalize_material_effects(list/materials)
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index f1adb1b03f0ec..469e5e49bc5a7 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -317,6 +317,10 @@ Security HUDs! Basic mode shows only the job.
set_hud_image_active(IMPLOYAL_HUD)
/mob/living/carbon/human/proc/sec_hud_set_security_status()
+ if(!hud_list)
+ // We haven't finished initializing yet, huds will be updated once we are
+ return
+
var/image/holder = hud_list[WANTED_HUD]
var/icon/sec_icon = icon(icon, icon_state, dir)
holder.pixel_y = sec_icon.Height() - ICON_SIZE_Y
diff --git a/code/game/machinery/dna_infuser/dna_infusion.dm b/code/game/machinery/dna_infuser/dna_infusion.dm
index c902240404ca7..86a8a5f41e9f3 100644
--- a/code/game/machinery/dna_infuser/dna_infusion.dm
+++ b/code/game/machinery/dna_infuser/dna_infusion.dm
@@ -47,6 +47,8 @@
// Valid organ successfully picked.
new_organ = new new_organ()
new_organ.replace_into(src)
+ //make sure bodypart overlays are correctly displayed.
+ update_body_parts()
return TRUE
/// Picks a random mutated organ from the given infuser entry which is also compatible with this human.
diff --git a/code/game/machinery/dna_infuser/infuser_actions.dm b/code/game/machinery/dna_infuser/infuser_actions.dm
new file mode 100644
index 0000000000000..1b55059bb9899
--- /dev/null
+++ b/code/game/machinery/dna_infuser/infuser_actions.dm
@@ -0,0 +1,61 @@
+///Action from the inky tongue, from fish with the ink production trait.
+/datum/action/cooldown/ink_spit
+ name = "Spit Ink"
+ desc = "Spits ink at someone, blinding them temporarily."
+ button_icon = 'icons/hud/radial_fishing.dmi'
+ button_icon_state = "oil"
+ base_background_icon_state = "bg_default"
+ active_background_icon_state = "bg_default_on"
+ check_flags = AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
+ click_to_activate = TRUE
+ unset_after_click = TRUE
+ cooldown_time = 21 SECONDS
+
+/datum/action/cooldown/ink_spit/IsAvailable(feedback = FALSE)
+ var/mob/living/carbon/as_carbon = owner
+ if(istype(as_carbon) && as_carbon.is_mouth_covered(ITEM_SLOT_MASK))
+ return FALSE
+ if(!isturf(owner.loc))
+ return FALSE
+ return ..()
+
+/datum/action/cooldown/ink_spit/set_click_ability(mob/on_who)
+ . = ..()
+ if(!.)
+ return
+
+ to_chat(on_who, span_notice("You prepare your ink glands. Right-click to fire at a target!"))
+ build_all_button_icons()
+
+/datum/action/cooldown/ink_spit/unset_click_ability(mob/on_who, refund_cooldown = TRUE)
+ . = ..()
+ if(!.)
+ return
+
+ build_all_button_icons()
+
+// We do this in InterceptClickOn() instead of Activate()
+// because we use the click parameters for aiming the projectile
+// (or something like that)
+/datum/action/cooldown/ink_spit/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(!LAZYACCESS(params2list(params), RIGHT_CLICK))
+ return
+ . = ..()
+
+ var/modifiers = params2list(params)
+ caller.visible_message(
+ span_danger("[caller] spits ink!"),
+ span_bold("You spit ink."),
+ )
+ var/obj/projectile/ink_spit/ink = new /obj/projectile/ink_spit(caller.loc)
+ ink.preparePixelProjectile(target, caller, modifiers)
+ ink.firer = caller
+ ink.fire()
+ playsound(caller, 'sound/items/weapons/pierce.ogg', 20, TRUE, -1)
+ caller.newtonian_move(get_angle(target, caller))
+ StartCooldown()
+ return TRUE
+
+// Has to return TRUE, otherwise is skipped.
+/datum/action/cooldown/ink_spit/Activate(atom/target)
+ return TRUE
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 bd8734643f898..faa3683b9a27b 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
@@ -105,3 +105,86 @@
infusion_desc = "kafkaesque" // Gregor Samsa !!
tier = DNA_MUTANT_TIER_ONE
status_effect_type = /datum/status_effect/organ_set_bonus/roach
+
+/datum/infuser_entry/fish
+ name = "Fish"
+ infuse_mob_name = "fish"
+ desc = "Aquatic life comes in several forms. A fisherman could tell you more about it, but that's beside the point. \
+ This infusion comes with many benefits and one potential major drawback being fish-mutated lungs, with \
+ additional organs depending on the traits of the fish used for the infusion."
+ threshold_desc = "While wet, you're slightly sturdier, immune to slips, and both slippery and faster while crawling. \
+ Drinking water and showers heal you, and it takes longer to dry out, however you're weaker when dry. \
+ Finally, you resist high pressures and are better at fishing. "
+ qualities = list(
+ "faster in water",
+ "resistant to food diseases",
+ "enjoy eating raw fish",
+ "flopping and waddling",
+ "fishing is easier",
+ "Need water. badly!",
+ "possibly more",
+ )
+ input_obj_or_mob = list(
+ /obj/item/fish,
+ )
+ output_organs = list(
+ /obj/item/organ/internal/lungs/fish,
+ /obj/item/organ/internal/stomach/fish,
+ /obj/item/organ/external/tail/fish,
+ )
+ infusion_desc = "piscine"
+ tier = DNA_MUTANT_TIER_ONE
+ status_effect_type = /datum/status_effect/organ_set_bonus/fish
+
+/datum/infuser_entry/squid
+ name = "Ink Production"
+ infuse_mob_name = "ink-producing sealife"
+ desc = "Some marine mollusks like cuttlefish, squids and octopus release ink when threatened as a smokescreen for their escape. \
+ This kind of infusion enhances the salivary glands, producing excessive quantities of ink which can later be spat to blind foes."
+ threshold_desc = DNA_INFUSION_NO_THRESHOLD
+ qualities = list(
+ "spit ink to blind foes",
+ )
+ output_organs = list(
+ /obj/item/organ/internal/tongue/inky
+ )
+ tier = DNA_MUTANT_TIER_ONE
+
+/datum/infuser_entry/ttx_healing
+ name = "TTX healing"
+ infuse_mob_name = "Tetraodontiformes"
+ desc = "Fish of the Tetraodontiformes (pufferfish etc.) order are known for the highly poisonous tetrodotoxin (TTX) in their bodies. \
+ Extracting their DNA can provide a way to utilize it for healing instead. It also enables better alcohol metabolization."
+ threshold_desc = DNA_INFUSION_NO_THRESHOLD
+ qualities = list(
+ "TTX healing",
+ "drink like a fish",
+ )
+ output_organs = list(
+ /obj/item/organ/internal/liver/fish
+ )
+ tier = DNA_MUTANT_TIER_ONE
+ unreachable_effect = TRUE
+ status_effect_type = /datum/status_effect/organ_set_bonus/fish
+
+/datum/infuser_entry/amphibious
+ name = "Amphibious"
+ infuse_mob_name = "Semi-aquatic critters"
+ desc = "Some animals breathe air, some breath water, a few can breath both, even if none (at least on Earth) can breathe in space."
+ threshold_desc = DNA_INFUSION_NO_THRESHOLD
+ qualities = list(
+ "no need to breathe while wet",
+ "can beathe water vapor",
+ )
+ input_obj_or_mob = list(
+ /mob/living/basic/frog,
+ /mob/living/basic/axolotl,
+ /mob/living/basic/crab,
+ )
+ output_organs = list(
+ /obj/item/organ/internal/lungs/fish/amphibious,
+ )
+ infusion_desc = "semi-aquatic"
+ tier = DNA_MUTANT_TIER_ONE
+ unreachable_effect = TRUE
+ status_effect_type = /datum/status_effect/organ_set_bonus/fish
diff --git a/code/game/machinery/dna_infuser/infuser_entry.dm b/code/game/machinery/dna_infuser/infuser_entry.dm
index 8b0bcfb3f790d..55ac43d1bf4e6 100644
--- a/code/game/machinery/dna_infuser/infuser_entry.dm
+++ b/code/game/machinery/dna_infuser/infuser_entry.dm
@@ -28,8 +28,16 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_infuser_entries())
)
/// status effect type of the corresponding bonus, if it has one. tier zero won't ever set this.
var/status_effect_type
- /// essentially how difficult it is to get this infusion, and if it will be locked behind some progression. see defines for more info
- /// ...overwrite this, please
+ /**
+ * This var clarifies that while the infuser entry has organs that contribute towards an organ set bonus
+ * It cannot reach the organ threshold of the bonus on its own, meaning it relies on some other infuser entry for that.
+ * This is mainly the case for fish organs from fish with specific traits, for example. We don't want the unit test to bith about it.
+ */
+ var/unreachable_effect = FALSE
+ /**
+ * essentially how difficult it is to get this infusion, and if it will be locked behind some progression. see defines for more info
+ * ...overwrite this, please
+ */
var/tier = DNA_MUTANT_UNOBTAINABLE
//-- Vars for DNA Infuser Machine --//
diff --git a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm
index 10fcae90a9591..c551ce0c4e964 100644
--- a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm
+++ b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm
@@ -10,6 +10,7 @@
bonus_activate_text = span_notice("Carp DNA is deeply infused with you! You've learned how to propel yourself through space!")
bonus_deactivate_text = span_notice("Your DNA is once again mostly yours, and so fades your ability to space-swim...")
bonus_traits = list(TRAIT_SPACEWALK)
+ limb_overlay = /datum/bodypart_overlay/texture/carpskin
///Carp lungs! You can breathe in space! Oh... you can't breathe on the station, you need low oxygen environments.
/// Inverts behavior of lungs. Bypasses suffocation due to space / lack of gas, but also allows Oxygen to suffocate.
diff --git a/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm
new file mode 100644
index 0000000000000..c2eeb0e7521a0
--- /dev/null
+++ b/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm
@@ -0,0 +1,388 @@
+#define FISH_ORGAN_COLOR "#875652" //dark moderate magenta
+#define FISH_SCLERA_COLOR COLOR_WHITE
+#define FISH_PUPIL_COLOR COLOR_BLUE
+#define FISH_COLORS FISH_ORGAN_COLOR + FISH_SCLERA_COLOR + FISH_PUPIL_COLOR
+
+///bonus of the observing gondola: you can ignore environmental hazards
+/datum/status_effect/organ_set_bonus/fish
+ id = "organ_set_bonus_fish"
+ tick_interval = 1 SECONDS
+ organs_needed = 3
+ bonus_activate_text = span_notice("Fish DNA is deeply infused with you! While wet, you crawl faster, are slippery, and cannot slip, and it takes longer to dry out. \
+ You're also more resistant to high pressure, better at fishing, but less resilient when dry, especially against burns.")
+ bonus_deactivate_text = span_notice("You no longer feel as fishy. The moisture around your body begins to dissipate faster...")
+ bonus_traits = list(
+ TRAIT_RESISTHIGHPRESSURE,
+ TRAIT_EXPERT_FISHER,
+ TRAIT_EXAMINE_FISH,
+ TRAIT_EXAMINE_DEEPER_FISH,
+ TRAIT_REVEAL_FISH,
+ TRAIT_EXAMINE_FISHING_SPOT,
+ TRAIT_WET_FOR_LONGER,
+ TRAIT_SLIPPERY_WHEN_WET,
+ TRAIT_EXPANDED_FOV, //fish vision
+ TRAIT_WATER_ADAPTATION,
+ )
+
+/datum/status_effect/organ_set_bonus/fish/enable_bonus()
+ . = ..()
+ if(!.)
+ return
+ RegisterSignals(owner, list(COMSIG_CARBON_GAIN_ORGAN, COMSIG_CARBON_LOSE_ORGAN), PROC_REF(check_tail))
+ RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_IS_WET), SIGNAL_REMOVETRAIT(TRAIT_IS_WET)), PROC_REF(update_wetness))
+ RegisterSignals(owner, COMSIG_LIVING_GET_PERCEIVED_FOOD_QUALITY, PROC_REF(get_perceived_food_quality))
+
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human = owner
+ human.physiology.damage_resistance += 8 //base 8% damage resistance, much wow.
+ if(!HAS_TRAIT(owner, TRAIT_IS_WET))
+ apply_debuff()
+ else
+ ADD_TRAIT(owner, TRAIT_GRABRESISTANCE, REF(src))
+ owner.add_mood_event("fish_organs_bonus", /datum/mood_event/fish_water)
+ if(HAS_TRAIT(owner, TRAIT_IS_WET) && istype(owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL), /obj/item/organ/external/tail/fish))
+ add_speed_buff()
+ owner.mind?.adjust_experience(/datum/skill/fishing, SKILL_EXP_JOURNEYMAN, silent = TRUE)
+
+/datum/status_effect/organ_set_bonus/fish/disable_bonus()
+ . = ..()
+ UnregisterSignal(owner, list(
+ COMSIG_CARBON_GAIN_ORGAN,
+ COMSIG_CARBON_LOSE_ORGAN,
+ SIGNAL_ADDTRAIT(TRAIT_IS_WET),
+ SIGNAL_REMOVETRAIT(TRAIT_IS_WET),
+ COMSIG_LIVING_TREAT_MESSAGE,
+ COMSIG_LIVING_GET_PERCEIVED_FOOD_QUALITY,
+ ))
+ if(!HAS_TRAIT(owner, TRAIT_IS_WET))
+ remove_debuff()
+ else
+ REMOVE_TRAIT(owner, TRAIT_GRABRESISTANCE, REF(src))
+ owner.clear_mood_event("fish_organs_bonus")
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human = owner
+ human.physiology.damage_resistance -= 8
+ if(HAS_TRAIT(owner, TRAIT_IS_WET) && istype(owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL), /obj/item/organ/external/tail/fish))
+ remove_speed_buff()
+ owner.mind?.adjust_experience(/datum/skill/fishing, -SKILL_EXP_JOURNEYMAN, silent = TRUE)
+
+/datum/status_effect/organ_set_bonus/fish/proc/get_perceived_food_quality(datum/source, datum/component/edible/edible, list/extra_quality)
+ SIGNAL_HANDLER
+ if(HAS_TRAIT(edible.parent, TRAIT_GREAT_QUALITY_BAIT))
+ extra_quality += LIKED_FOOD_QUALITY_CHANGE * 3
+ else if(HAS_TRAIT(edible.parent, TRAIT_GOOD_QUALITY_BAIT))
+ extra_quality += LIKED_FOOD_QUALITY_CHANGE * 2
+ else if(HAS_TRAIT(edible.parent, TRAIT_BASIC_QUALITY_BAIT))
+ extra_quality += LIKED_FOOD_QUALITY_CHANGE
+
+/datum/status_effect/organ_set_bonus/fish/tick(seconds_between_ticks)
+ . = ..()
+ if(!bonus_active || !HAS_TRAIT(owner, TRAIT_IS_WET))
+ return
+ owner.adjust_bodytemperature(-2 * seconds_between_ticks, min_temp = owner.get_body_temp_normal())
+ owner.adjustStaminaLoss(-1.5 * seconds_between_ticks)
+
+/datum/status_effect/organ_set_bonus/fish/proc/update_wetness(datum/source)
+ SIGNAL_HANDLER
+ if(HAS_TRAIT(owner, TRAIT_IS_WET)) //remove the debuffs from being dry
+ remove_debuff()
+ if(istype(owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL), /obj/item/organ/external/tail/fish))
+ add_speed_buff()
+ return
+ apply_debuff()
+ if(istype(owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL), /obj/item/organ/external/tail/fish))
+ remove_speed_buff()
+
+/datum/status_effect/organ_set_bonus/fish/proc/apply_debuff()
+ REMOVE_TRAIT(owner, TRAIT_GRABRESISTANCE, REF(src))
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/fish_waterless)
+ owner.add_mood_event("fish_organs_bonus", /datum/mood_event/fish_waterless)
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/human = owner
+ human.physiology.burn_mod *= 1.5
+ human.physiology.heat_mod *= 1.2
+ human.physiology.brute_mod *= 1.1
+ human.physiology.stun_mod *= 1.1
+ human.physiology.knockdown_mod *= 1.1
+ human.physiology.stamina_mod *= 1.1
+ human.physiology.damage_resistance -= 16 //from +8% to -8%
+
+/datum/status_effect/organ_set_bonus/fish/proc/remove_debuff()
+ ADD_TRAIT(owner, TRAIT_GRABRESISTANCE, REF(src)) //harder to grab when wet.
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/fish_waterless)
+ owner.add_mood_event("fish_organs_bonus", /datum/mood_event/fish_water)
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/human = owner
+ human.physiology.burn_mod /= 1.5
+ human.physiology.heat_mod /= 1.2
+ human.physiology.brute_mod /= 1.1
+ human.physiology.stun_mod /= 1.1
+ human.physiology.knockdown_mod /= 1.1
+ human.physiology.stamina_mod /= 1.1
+ human.physiology.damage_resistance += 16 //from -8% to +8%
+
+/datum/status_effect/organ_set_bonus/fish/proc/check_tail(mob/living/carbon/source, obj/item/organ/organ, special)
+ SIGNAL_HANDLER
+ if(!HAS_TRAIT(owner, TRAIT_IS_WET) || !istype(organ, /obj/item/organ/external/tail/fish))
+ return
+ var/obj/item/organ/tail = owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL)
+ if(tail != organ)
+ remove_speed_buff()
+ return
+ add_speed_buff()
+
+/datum/status_effect/organ_set_bonus/fish/proc/add_speed_buff(datum/source)
+ SIGNAL_HANDLER
+ RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(check_body_position))
+ check_body_position()
+
+/datum/status_effect/organ_set_bonus/fish/proc/remove_speed_buff(datum/source)
+ SIGNAL_HANDLER
+ UnregisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION)
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/fish_flopping)
+
+/datum/status_effect/organ_set_bonus/fish/proc/check_body_position(datum/source)
+ SIGNAL_HANDLER
+ if(owner.body_position == LYING_DOWN)
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/fish_flopping)
+ else
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/fish_flopping)
+
+
+///Tail for fish DNA-infused spacemen. It provides a speed buff while in water. It's also needed for the crawl speed bonus once the threshold is reached.
+/obj/item/organ/external/tail/fish
+ name = "fish tail"
+ desc = "A severed tail from some sort of marine creature... or a fish-infused spaceman. It's smooth, faintly wet and definitely not flopping."
+ icon = 'icons/obj/medical/organs/infuser_organs.dmi'
+ icon_state = "fish_tail"
+ greyscale_config = /datum/greyscale_config/fish_tail
+ greyscale_colors = FISH_ORGAN_COLOR
+
+ bodypart_overlay = /datum/bodypart_overlay/mutant/tail/fish
+ dna_block = DNA_FISH_TAIL_BLOCK
+ wag_flags = WAG_ABLE
+ organ_traits = list(TRAIT_FLOPPING)
+
+/obj/item/organ/external/tail/fish/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fish)
+
+/obj/item/organ/external/tail/fish/on_mob_insert(mob/living/carbon/owner)
+ . = ..()
+ owner.AddElementTrait(TRAIT_WADDLING, type, /datum/element/waddling)
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(check_location))
+ check_location(owner, null)
+
+/obj/item/organ/external/tail/fish/on_mob_remove(mob/living/carbon/owner)
+ . = ..()
+ owner.remove_traits(list(TRAIT_WADDLING, TRAIT_NO_STAGGER), type)
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/fish_on_water)
+ owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/fish_on_water)
+ UnregisterSignal(owner, COMSIG_MOVABLE_MOVED)
+
+/obj/item/organ/external/tail/fish/get_greyscale_color_from_draw_color()
+ set_greyscale(bodypart_overlay.draw_color)
+
+/obj/item/organ/external/tail/fish/proc/check_location(mob/living/carbon/source, atom/movable/old_loc, dir, forced)
+ SIGNAL_HANDLER
+ var/was_water = istype(old_loc, /turf/open/water)
+ var/is_water = istype(source.loc, /turf/open/water) && !HAS_TRAIT(source.loc, TRAIT_TURF_IGNORE_SLOWDOWN)
+ if(was_water && !is_water)
+ source.remove_movespeed_modifier(/datum/movespeed_modifier/fish_on_water)
+ source.remove_actionspeed_modifier(/datum/actionspeed_modifier/fish_on_water)
+ source.add_traits(list(TRAIT_OFF_BALANCE_TACKLER, TRAIT_NO_STAGGER, TRAIT_NO_THROW_HITPUSH), type)
+ else if(!was_water && is_water)
+ source.add_movespeed_modifier(/datum/movespeed_modifier/fish_on_water)
+ source.add_actionspeed_modifier(/datum/actionspeed_modifier/fish_on_water)
+ source.add_traits(list(TRAIT_OFF_BALANCE_TACKLER, TRAIT_NO_STAGGER, TRAIT_NO_THROW_HITPUSH), type)
+
+/datum/bodypart_overlay/mutant/tail/fish
+ feature_key = "fish_tail"
+ color_source = ORGAN_COLOR_HAIR
+
+/datum/bodypart_overlay/mutant/tail/fish/on_mob_insert(obj/item/organ/parent, mob/living/carbon/receiver)
+ //Initialize the related dna feature block if we don't have any so it doesn't error out.
+ //This isn't tied to any species, but I kinda want it to be mutable instead of having a fixed sprite accessory.
+ if(imprint_on_next_insertion && !receiver.dna.features["fish_tail"])
+ receiver.dna.features["fish_tail"] = pick(SSaccessories.tails_list_fish)
+ receiver.dna.update_uf_block(DNA_FISH_TAIL_BLOCK)
+
+ return ..()
+
+/datum/bodypart_overlay/mutant/tail/fish/get_global_feature_list()
+ return SSaccessories.tails_list_fish
+
+
+///Lungs that replace the need of oxygen with water vapor or being wet
+/obj/item/organ/internal/lungs/fish
+ name = "mutated gills"
+ desc = "Fish DNA infused on what once was a normal pair of lungs that now require spacemen to breathe water vapor, or keep themselves covered in water."
+ icon = 'icons/obj/medical/organs/infuser_organs.dmi'
+ icon_state = "gills"
+
+ safe_oxygen_min = 0 //We don't breathe this
+ ///The required partial pressure of water_vapor for not drowing
+ var/safe_water_level = 29
+
+ /// Bodypart overlay applied to the chest where the lungs are in
+ var/datum/bodypart_overlay/simple/gills/gills
+
+ var/has_gills = TRUE
+
+/obj/item/organ/internal/lungs/fish/Initialize(mapload)
+ . = ..()
+ add_gas_reaction(/datum/gas/water_vapor, always = PROC_REF(breathe_water))
+ respiration_type |= RESPIRATION_OXYGEN //after all, we get oxygen from water
+ AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fish)
+ if(has_gills)
+ gills = new()
+ AddElement(/datum/element/noticable_organ, "%PRONOUN_Theyve a set of gills on %PRONOUN_their neck.", BODY_ZONE_PRECISE_MOUTH)
+ AddComponent(/datum/component/bubble_icon_override, "fish", BUBBLE_ICON_PRIORITY_ORGAN)
+
+/obj/item/organ/internal/lungs/fish/Destroy()
+ QDEL_NULL(gills)
+ return ..()
+
+/obj/item/organ/internal/lungs/fish/on_bodypart_insert(obj/item/bodypart/limb)
+ . = ..()
+ if(gills)
+ limb.add_bodypart_overlay(gills)
+
+/obj/item/organ/internal/lungs/fish/on_bodypart_remove(obj/item/bodypart/limb)
+ . = ..()
+ if(gills)
+ limb.remove_bodypart_overlay(gills)
+
+/obj/item/organ/internal/lungs/fish/on_mob_remove(mob/living/carbon/owner)
+ . = ..()
+ owner.clear_alert(ALERT_NOT_ENOUGH_WATER)
+
+/// Requires the spaceman to have either water vapor or be wet.
+/obj/item/organ/internal/lungs/fish/proc/breathe_water(mob/living/carbon/breather, datum/gas_mixture/breath, water_pp, old_water_pp)
+ var/need_to_breathe = !HAS_TRAIT(src, TRAIT_SPACEBREATHING) && !HAS_TRAIT(breather, TRAIT_IS_WET)
+ if(water_pp < safe_water_level && need_to_breathe)
+ on_low_water(breather, breath, water_pp)
+ return
+
+ if(old_water_pp < safe_water_level || breather.failed_last_breath)
+ breather.failed_last_breath = FALSE
+ breather.clear_alert(ALERT_NOT_ENOUGH_WATER)
+
+ if(need_to_breathe)
+ breathe_gas_volume(breath, /datum/gas/water_vapor, /datum/gas/carbon_dioxide)
+ // Heal mob if not in crit.
+ if(breather.health >= breather.crit_threshold && breather.oxyloss)
+ breather.adjustOxyLoss(-5)
+
+/// Called when there isn't enough water to breath
+/obj/item/organ/internal/lungs/fish/proc/on_low_water(mob/living/carbon/breather, datum/gas_mixture/breath, water_pp)
+ breather.throw_alert(ALERT_NOT_ENOUGH_WATER, /atom/movable/screen/alert/not_enough_water)
+ var/gas_breathed = handle_suffocation(breather, water_pp, safe_water_level, breath.gases[/datum/gas/water_vapor][MOLES])
+ if(water_pp)
+ breathe_gas_volume(breath, /datum/gas/water_vapor, /datum/gas/carbon_dioxide, volume = gas_breathed)
+
+// Simple overlay so we can add gills to those with fish lungs
+/datum/bodypart_overlay/simple/gills
+ icon = 'icons/mob/human/fish_features.dmi'
+ icon_state = "gills"
+ layers = EXTERNAL_ADJACENT
+
+/datum/bodypart_overlay/simple/gills/get_image(image_layer, obj/item/bodypart/limb)
+ return image(
+ icon = icon,
+ icon_state = "[icon_state]_[mutant_bodyparts_layertext(image_layer)]",
+ layer = image_layer,
+ )
+
+/// Subtype of gills that allow the mob to optionally breathe water.
+/obj/item/organ/internal/lungs/fish/amphibious
+ name = "mutated semi-aquatic lungs"
+ desc = "DNA from an amphibious or semi-aquatic creature infused on a pair lungs. Enjoy breathing underwater without drowning outside water."
+ safe_oxygen_min = /obj/item/organ/internal/lungs::safe_oxygen_min
+ safe_water_level = 19
+ has_gills = FALSE
+ /**
+ * If false, we don't breathe air since we've got water instead.
+ * Set to FALSE at the start of each cycle and TRUE on on_low_water()
+ */
+ var/should_breathe_oxygen = FALSE
+
+/obj/item/organ/internal/lungs/fish/amphibious/Initialize(mapload)
+ . = ..()
+ /**
+ * We're setting the gas reaction for breathing oxygen here,
+ * since gas reation procs are run in the order they're added,
+ * and we want breathe_water() to run before breathe_oxygen,
+ * so that if we're breathing water vapor (or are wet), we won't have to breathe oxygen.
+ */
+ safe_oxygen_min = /obj/item/organ/internal/lungs::safe_oxygen_min
+ add_gas_reaction(/datum/gas/oxygen, always = PROC_REF(breathe_oxygen))
+
+/obj/item/organ/internal/lungs/fish/amphibious/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/breather)
+ should_breathe_oxygen = FALSE //assume we don't have to breathe oxygen until we fail to breathe water
+ return ..()
+
+/obj/item/organ/internal/lungs/fish/amphibious/on_low_water(mob/living/carbon/breather, datum/gas_mixture/breath, water_pp)
+ should_breathe_oxygen = TRUE
+ return
+
+/obj/item/organ/internal/lungs/fish/amphibious/breathe_oxygen(mob/living/carbon/breather, datum/gas_mixture/breath, o2_pp, old_o2_pp)
+ if(!should_breathe_oxygen)
+ if(breather.failed_last_breath) //in case we had neither oxygen nor water last tick.
+ breather.clear_alert(ALERT_NOT_ENOUGH_OXYGEN)
+ return
+ return ..()
+
+///Fish infuser organ, allows mobs to safely eat raw fish.
+/obj/item/organ/internal/stomach/fish
+ name = "mutated fish-stomach"
+ desc = "Fish DNA infused into a stomach now parmated by the faint smell of salt and slightly putrified fish."
+ icon = 'icons/obj/medical/organs/infuser_organs.dmi'
+ icon_state = "stomach"
+ greyscale_config = /datum/greyscale_config/mutant_organ
+ greyscale_colors = FISH_COLORS
+
+ organ_traits = list(TRAIT_STRONG_STOMACH, TRAIT_FISH_EATER)
+ disgust_metabolism = 2.5
+
+/obj/item/organ/internal/stomach/fish/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fish)
+
+
+///Organ from fish with the ink production trait. Doesn't count toward the organ set bonus but is buffed once it's active.
+/obj/item/organ/internal/tongue/inky
+ name = "ink-secreting tongue"
+ desc = "A black tongue linked to two swollen black sacs underneath the palate."
+ icon = 'icons/obj/medical/organs/infuser_organs.dmi'
+ icon_state = "inky_tongue"
+ actions_types = list(/datum/action/cooldown/ink_spit)
+
+/obj/item/organ/internal/tongue/inky/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/noticable_organ, "Slick black ink seldom rivulets from %PRONOUN_their mouth.", BODY_ZONE_PRECISE_MOUTH)
+
+///Organ from fish with the toxic trait. Allows the user to use tetrodotoxin as a healing chem instead of a toxin.
+/obj/item/organ/internal/liver/fish
+ name = "mutated fish-liver"
+ desc = "Fish DNA infused into a stomach that now uses tetrodotoxin as regenerative material. It also processes alcohol quite well."
+ icon = 'icons/obj/medical/organs/infuser_organs.dmi'
+ icon_state = "liver"
+ greyscale_config = /datum/greyscale_config/mutant_organ
+ greyscale_colors = FISH_COLORS
+
+ organ_traits = list(TRAIT_TETRODOTOXIN_HEALING, TRAIT_ALCOHOL_TOLERANCE) //drink like a fish :^)
+ liver_resistance = parent_type::liver_resistance * 1.5
+ food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/iron = 5, /datum/reagent/toxin/tetrodotoxin = 5)
+ grind_results = list(/datum/reagent/consumable/nutriment/peptides = 5, /datum/reagent/toxin/tetrodotoxin = 5)
+
+/obj/item/organ/internal/liver/fish/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fish)
+
+#undef FISH_ORGAN_COLOR
+#undef FISH_SCLERA_COLOR
+#undef FISH_PUPIL_COLOR
+#undef FISH_COLORS
diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm
index 1467a7854be52..68a49b8a3031b 100644
--- a/code/game/objects/effects/step_triggers.dm
+++ b/code/game/objects/effects/step_triggers.dm
@@ -220,3 +220,9 @@
if(happens_once)
qdel(src)
+
+/obj/effect/step_trigger/sound_effect/lavaland_cult_altar
+ happens_once = 1
+ name = "a grave mistake";
+ sound = 'sound/effects/hallucinations/i_see_you1.ogg'
+ triggerer_only = 1
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index a694b18d3388a..033f83edfc739 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -402,7 +402,12 @@
duration = 6
/obj/effect/temp_visual/impact_effect/neurotoxin
- icon_state = "impact_neurotoxin"
+ icon_state = "impact_spit"
+ color = "#5BDD04"
+
+/obj/effect/temp_visual/impact_effect/ink_spit
+ icon_state = "impact_spit"
+ color = COLOR_NEARLY_ALL_BLACK
/obj/effect/temp_visual/heart
name = "heart"
diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm
index 960363685b1e8..e46bb676a8caf 100644
--- a/code/game/objects/items/cardboard_cutouts.dm
+++ b/code/game/objects/items/cardboard_cutouts.dm
@@ -360,3 +360,34 @@
applied_name = "Private Security Officer"
applied_desc = "A cardboard cutout of a private security officer."
mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasensoldier
+
+/datum/cardboard_cutout/heretic
+ name = "Heretic"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a Heretic."
+ outfit = /datum/outfit/heretic_hallucination
+
+/datum/cardboard_cutout/changeling
+ name = "Changeling"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a Changeling."
+ outfit = /datum/outfit/changeling
+
+/datum/cardboard_cutout/pirate
+ name = "Pirate"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a space pirate."
+ outfit = /datum/outfit/pirate/space/captain/cardboard
+
+/datum/cardboard_cutout/ninja
+ name = "Space Ninja"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a space ninja."
+ outfit = /datum/outfit/ninja
+
+/datum/cardboard_cutout/abductor
+ name = "Abductor Agent"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of an abductor agent."
+ species = /datum/species/abductor
+ outfit = /datum/outfit/abductor/agent/cardboard
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 270c070d6dc5e..5b7ee4f7026b8 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -280,7 +280,7 @@
var/list/missing_organs = list()
if(!humantarget.get_organ_slot(ORGAN_SLOT_BRAIN))
missing_organs[ORGAN_SLOT_BRAIN] = "Brain"
- if(!humantarget.needs_heart() && !humantarget.get_organ_slot(ORGAN_SLOT_HEART))
+ if(humantarget.needs_heart() && !humantarget.get_organ_slot(ORGAN_SLOT_HEART))
missing_organs[ORGAN_SLOT_HEART] = "Heart"
if(!HAS_TRAIT_FROM(humantarget, TRAIT_NOBREATH, SPECIES_TRAIT) && !isnull(humantarget.dna.species.mutantlungs) && !humantarget.get_organ_slot(ORGAN_SLOT_LUNGS))
missing_organs[ORGAN_SLOT_LUNGS] = "Lungs"
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index 9c4192116a003..b4150ecb72fea 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -6,6 +6,8 @@
worn_icon_state = "fire_extinguisher"
inhand_icon_state = "fire_extinguisher"
hitsound = 'sound/items/weapons/smash.ogg'
+ pickup_sound = 'sound/items/handling/gas_tank/gas_tank_pick_up.ogg'
+ drop_sound = 'sound/items/handling/gas_tank/gas_tank_drop.ogg'
obj_flags = CONDUCTS_ELECTRICITY
throwforce = 10
w_class = WEIGHT_CLASS_NORMAL
@@ -47,6 +49,9 @@
var/cooling_power = 2
/// Icon state when inside a tank holder.
var/tank_holder_icon_state = "holder_extinguisher"
+ ///The sound a fire extinguisher makes when picked up, dropped if there is liquid inside.
+ var/fire_extinguisher_reagent_sloshing_sound = SFX_DEFAULT_LIQUID_SLOSH
+
/obj/item/extinguisher/Initialize(mapload)
. = ..()
@@ -66,6 +71,17 @@
context[SCREENTIP_CONTEXT_ALT_LMB] = "Empty"
return CONTEXTUAL_SCREENTIP_SET
+/obj/item/extinguisher/dropped(mob/user, silent)
+ . = ..()
+ if(fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
+ playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
+
+/obj/item/extinguisher/equipped(mob/user, slot, initial = FALSE)
+ . = ..()
+ if((slot & ITEM_SLOT_HANDS) && fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
+ playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
+
+
/obj/item/extinguisher/empty
starting_water = FALSE
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 95d99d64ef80d..9a1b8bed101f0 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -531,7 +531,7 @@
///Cleric maces are made of two custom materials: one is handle, and the other is the mace itself.
/obj/item/melee/cleric_mace/get_material_multiplier(datum/material/custom_material, list/materials, index)
- if(length(materials) < 1)
+ if(length(materials) <= 1)
return 1.2
if(index == 1)
return 1
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 6f47661d93a63..c9270b1f67ef6 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -1360,6 +1360,11 @@
toysay = "EI NATH!"
toysound = 'sound/effects/magic/disintegrate.ogg'
+/obj/item/toy/figure/wizard/special
+ name = "\improper Wizard action figure special edition"
+ toysay = "CLANG!";
+ toysound = 'sound/effects/clang.ogg'
+
/obj/item/toy/figure/rd
name = "\improper Research Director action figure"
icon_state = "rd"
diff --git a/code/game/sound.dm b/code/game/sound.dm
index f1fec41cf692d..a4b3f8e260096 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -558,4 +558,25 @@
'sound/creatures/cat/cat_purr3.ogg',
'sound/creatures/cat/cat_purr4.ogg',
)
+ if(SFX_DEFAULT_LIQUID_SLOSH)
+ soundin = pick(
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh1.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh2.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh3.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh4.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh5.ogg',
+ )
+ if(SFX_PLASTIC_BOTTLE_LIQUID_SLOSH)
+ soundin = pick(
+ 'sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh1.ogg',
+ 'sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh2.ogg',
+ )
+ if(SFX_PLATE_ARMOR_RUSTLE)
+ soundin = pick_weight(list(
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle1.ogg' = 8, //longest sound is rarer.
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle2.ogg' = 23,
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle3.ogg' = 23,
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle4.ogg' = 23,
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle5.ogg' = 23,
+ ))
return soundin
diff --git a/code/game/turfs/open/chasm.dm b/code/game/turfs/open/chasm.dm
index f81ac4c7f536e..2699b4933626d 100644
--- a/code/game/turfs/open/chasm.dm
+++ b/code/game/turfs/open/chasm.dm
@@ -50,8 +50,8 @@
/turf/open/chasm/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "basalt"
+ underlay_appearance.icon = /turf/open/misc/asteroid/basalt::icon
+ underlay_appearance.icon_state = /turf/open/misc/asteroid/basalt::icon_state
return TRUE
/turf/open/chasm/attackby(obj/item/C, mob/user, params, area/area_restriction)
@@ -100,6 +100,11 @@
light_power = 0.65
light_color = LIGHT_COLOR_PURPLE
+/turf/open/chasm/icemoon/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ underlay_appearance.icon = /turf/open/misc/asteroid/snow/icemoon::icon
+ underlay_appearance.icon_state = /turf/open/misc/asteroid/snow/icemoon::icon_state
+ return TRUE
+
// Chasms for the jungle, with planetary atmos and a different icon
/turf/open/chasm/jungle
icon = 'icons/turf/floors/junglechasm.dmi'
@@ -109,8 +114,8 @@
baseturfs = /turf/open/chasm/jungle
/turf/open/chasm/jungle/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "dirt"
+ underlay_appearance.icon = /turf/open/misc/dirt::icon
+ underlay_appearance.icon_state = /turf/open/misc/dirt::icon_state
return TRUE
// Chasm that doesn't do any z-level nonsense and just kills/stores whoever steps into it.
diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm
index eebb74b72897b..1a9f24ce50ebd 100644
--- a/code/game/turfs/open/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -178,11 +178,6 @@
/turf/open/lava/singularity_pull(S, current_size)
return
-/turf/open/lava/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "basalt"
- return TRUE
-
/turf/open/lava/GetHeatCapacity()
. = 700000
@@ -341,6 +336,12 @@
underfloor_accessibility = 2 //This avoids strangeness when routing pipes / wires along catwalks over lava
immerse_overlay_color = "#F98511"
+/// Smooth lava needs to take after basalt in order to blend better. If you make a /turf/open/lava/smooth subtype for an area NOT surrounded by basalt; you should override this proc.
+/turf/open/lava/smooth/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ underlay_appearance.icon = /turf/open/misc/asteroid/basalt::icon
+ underlay_appearance.icon_state = /turf/open/misc/asteroid/basalt::icon_state
+ return TRUE
+
/turf/open/lava/smooth/lava_land_surface
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
planetary_atmos = TRUE
diff --git a/code/modules/actionspeed/modifiers/mobs.dm b/code/modules/actionspeed/modifiers/mobs.dm
new file mode 100644
index 0000000000000..adc1a1d120750
--- /dev/null
+++ b/code/modules/actionspeed/modifiers/mobs.dm
@@ -0,0 +1,3 @@
+///speed bonus given by the fish tail organ when inside water.
+/datum/actionspeed_modifier/fish_on_water
+ multiplicative_slowdown = -0.15
diff --git a/code/modules/admin/verbs/lawpanel.dm b/code/modules/admin/verbs/lawpanel.dm
index 6de3ff70182b8..32815b73cbd8f 100644
--- a/code/modules/admin/verbs/lawpanel.dm
+++ b/code/modules/admin/verbs/lawpanel.dm
@@ -48,7 +48,8 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
borgo.laws.add_inherent_law(lawtext)
if(LAW_SUPPLIED)
borgo.laws.add_supplied_law(length(borgo.laws.supplied), lawtext) // Just goes to the end of the list
-
+ log_admin("[key_name(user)] has UPLOADED a [lawtype] law to [key_name(borgo)] stating: [lawtext]")
+ message_admins("[key_name(user)] has UPLOADED a [lawtype] law to [key_name(borgo)] stating: [lawtext]")
return TRUE
/datum/law_panel/proc/move_law_helper(mob/living/user, mob/living/silicon/borgo, direction, law)
@@ -104,6 +105,9 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
return FALSE
relevant_laws[lawindex] = newlaw
+ log_admin("[key_name(user)] has EDITED [key_name(borgo)] [lawtype] law. OLD LAW: [oldlaw] \
+ NEW LAW: [newlaw]")
+ message_admins("[key_name(user)] has EDITED a [lawtype] law on [key_name(borgo)]")
return TRUE
/datum/law_panel/proc/edit_law_priority_helper(mob/living/user, mob/living/silicon/borgo, law)
@@ -145,10 +149,12 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
if(swap_or_remove == "Swap")
borgo.laws.supplied.Swap(old_prio, new_prio)
+ log_admin("[key_name(user)] has SWAPPED [key_name(borgo)] law [old_prio] and [new_prio]")
return TRUE
if(swap_or_remove == "Replace")
borgo.laws.remove_supplied_law_by_num(new_prio, law)
borgo.laws.add_supplied_law(new_prio, law)
+ log_admin("[key_name(user)] has REPLACED [key_name(borgo)] law: [law] with priority [new_prio]")
return TRUE
var/new_prio_for_old_law = new_prio + (swap_or_remove == "Move up" ? 1 : -1)
@@ -157,6 +163,7 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
borgo.laws.remove_supplied_law_by_num(new_prio)
borgo.laws.add_supplied_law(new_prio, law)
borgo.laws.add_supplied_law(new_prio_for_old_law, existing_law)
+ log_admin("[key_name(user)] has changed the priority of an existing law on [key_name(borgo)]. LAW: [law] PRIORITY: [new_prio]")
return TRUE
// Sanity
@@ -167,6 +174,8 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
// At this point the slot is free, insert it as normal
borgo.laws.remove_supplied_law_by_num(old_prio)
borgo.laws.add_supplied_law(new_prio, law)
+ log_admin("[key_name(user)] has UPLOADED a supplied law to [key_name(borgo)] stating: [law]") // Normal insertion, I.E upload
+ message_admins("[key_name(user)] has UPLOADED a supplied law to [key_name(borgo)] stating: [law]")
return TRUE
/datum/law_panel/proc/remove_law_helper(mob/living/user, mob/living/silicon/borgo, lawtype, law)
@@ -184,7 +193,8 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
borgo.laws.protected_zeroth = FALSE
else
return FALSE
-
+ log_admin("[key_name(user)] has REMOVED a law from [key_name(borgo)]. LAW: [law]")
+ message_admins("[key_name(user)] has REMOVED a law from [key_name(borgo)]. LAW: [law]")
return TRUE
/datum/law_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
diff --git a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
index 109f27e82225f..8739a9de74950 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
@@ -50,6 +50,13 @@
/obj/item/abductor/silencer = 1
)
+/datum/outfit/abductor/agent/cardboard
+ name = "Abductor Agent"
+ head = /obj/item/clothing/head/helmet/abductor
+ suit = /obj/item/clothing/suit/armor/abductor/vest
+ l_hand = /obj/item/melee/baton/abductor
+ belt = /obj/item/storage/belt/military/abductor/full
+
/datum/outfit/abductor/scientist
name = "Abductor Scientist"
diff --git a/code/modules/antagonists/heretic/magic/space_crawl.dm b/code/modules/antagonists/heretic/magic/space_crawl.dm
index 6a96403872f11..cce9f46085bc6 100644
--- a/code/modules/antagonists/heretic/magic/space_crawl.dm
+++ b/code/modules/antagonists/heretic/magic/space_crawl.dm
@@ -1,3 +1,5 @@
+#define SPACE_PHASING "space-phasing"
+
/**
* ### Space Crawl
*
@@ -16,6 +18,8 @@
invocation_type = INVOCATION_NONE
spell_requirements = NONE
+ ///List of traits that are added to the heretic while in space phase jaunt
+ var/static/list/jaunting_traits = list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD, TRAIT_NOBREATH)
/datum/action/cooldown/spell/jaunt/space_crawl/Grant(mob/grant_to)
. = ..()
@@ -82,6 +86,7 @@
jaunter.put_in_hands(left_hand)
jaunter.put_in_hands(right_hand)
+ jaunter.add_traits(jaunting_traits, SPACE_PHASING)
RegisterSignal(jaunter, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost))
playsound(our_turf, 'sound/effects/magic/cosmic_energy.ogg', 50, TRUE, -1)
our_turf.visible_message(span_warning("[jaunter] sinks into [our_turf]!"))
@@ -101,6 +106,7 @@
if(!exit_jaunt(jaunter, our_turf))
return FALSE
+ jaunter.remove_traits(jaunting_traits, SPACE_PHASING)
our_turf.visible_message(span_boldwarning("[jaunter] rises out of [our_turf]!"))
return TRUE
@@ -131,3 +137,5 @@
/obj/item/space_crawl/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+
+#undef SPACE_PHASING
diff --git a/code/modules/antagonists/pirate/pirate_outfits.dm b/code/modules/antagonists/pirate/pirate_outfits.dm
index 14729908e65be..8976e212c5475 100644
--- a/code/modules/antagonists/pirate/pirate_outfits.dm
+++ b/code/modules/antagonists/pirate/pirate_outfits.dm
@@ -57,6 +57,10 @@
id_trim = /datum/id_trim/pirate/captain
+/datum/outfit/pirate/space/captain/cardboard
+ name = "Space Pirate Captain (EVA)"
+ l_hand = /obj/item/nullrod/claymore/saber/pirate
+
/datum/outfit/pirate/silverscale
name = "Silver Scale Member"
diff --git a/code/modules/client/preferences/species_features/felinid.dm b/code/modules/client/preferences/species_features/felinid.dm
index be90d806323d3..5910e42894f02 100644
--- a/code/modules/client/preferences/species_features/felinid.dm
+++ b/code/modules/client/preferences/species_features/felinid.dm
@@ -1,32 +1,32 @@
-/datum/preference/choiced/tail_human
- savefile_key = "feature_human_tail"
+/datum/preference/choiced/tail_felinid
+ savefile_key = "feature_human_tail" //savefile keys cannot be changed, blame whoever named them this way.
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
can_randomize = FALSE
relevant_external_organ = /obj/item/organ/external/tail/cat
-/datum/preference/choiced/tail_human/init_possible_values()
- return assoc_to_keys_features(SSaccessories.tails_list_human)
+/datum/preference/choiced/tail_felinid/init_possible_values()
+ return assoc_to_keys_features(SSaccessories.tails_list_felinid)
-/datum/preference/choiced/tail_human/apply_to_human(mob/living/carbon/human/target, value)
+/datum/preference/choiced/tail_felinid/apply_to_human(mob/living/carbon/human/target, value)
target.dna.features["tail_cat"] = value
-/datum/preference/choiced/tail_human/create_default_value()
- var/datum/sprite_accessory/tails/human/cat/tail = /datum/sprite_accessory/tails/human/cat
+/datum/preference/choiced/tail_felinid/create_default_value()
+ var/datum/sprite_accessory/tails/felinid/cat/tail = /datum/sprite_accessory/tails/felinid/cat
return initial(tail.name)
-/datum/preference/choiced/ears
- savefile_key = "feature_human_ears"
+/datum/preference/choiced/felinid_ears
+ savefile_key = "feature_human_ears" //savefile keys cannot be changed, blame whoever named them this way.
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
can_randomize = FALSE
relevant_external_organ = /obj/item/organ/internal/ears/cat
-/datum/preference/choiced/ears/init_possible_values()
+/datum/preference/choiced/felinid_ears/init_possible_values()
return assoc_to_keys_features(SSaccessories.ears_list)
-/datum/preference/choiced/ears/apply_to_human(mob/living/carbon/human/target, value)
+/datum/preference/choiced/felinid_ears/apply_to_human(mob/living/carbon/human/target, value)
target.dna.features["ears"] = value
-/datum/preference/choiced/ears/create_default_value()
+/datum/preference/choiced/felinid_ears/create_default_value()
return /datum/sprite_accessory/ears/cat::name
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index ec1e2f303c44b..4569171c19a42 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -145,6 +145,10 @@
inhand_icon_state = "armor"
dog_fashion = null
+/obj/item/clothing/suit/armor/vest/cuirass/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/suit/armor/hos
name = "armored greatcoat"
desc = "A greatcoat enhanced with a special alloy for some extra protection and style for those with a commanding presence."
@@ -528,7 +532,7 @@
/obj/item/tank/internals/plasmaman,
)
/obj/item/clothing/suit/armor/riot/knight/init_rustle_component()
- return
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
/obj/item/clothing/suit/armor/riot/knight/yellow
icon_state = "knight_yellow"
diff --git a/code/modules/clothing/under/accessories/badges.dm b/code/modules/clothing/under/accessories/badges.dm
index 335eded4d4c82..9d7d87a084687 100644
--- a/code/modules/clothing/under/accessories/badges.dm
+++ b/code/modules/clothing/under/accessories/badges.dm
@@ -4,6 +4,10 @@
desc = "Fills you with the conviction of JUSTICE. Lawyers tend to want to show it to everyone they meet."
icon_state = "lawyerbadge"
+/obj/item/clothing/accessory/lawyers_badge/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/bubble_icon_override, "lawyer", BUBBLE_ICON_PRIORITY_ACCESSORY)
+
/obj/item/clothing/accessory/lawyers_badge/interact(mob/user)
. = ..()
if(prob(1))
@@ -12,11 +16,9 @@
/obj/item/clothing/accessory/lawyers_badge/accessory_equipped(obj/item/clothing/under/clothes, mob/living/user)
RegisterSignal(user, COMSIG_LIVING_SLAM_TABLE, PROC_REF(table_slam))
- user.bubble_icon = "lawyer"
/obj/item/clothing/accessory/lawyers_badge/accessory_dropped(obj/item/clothing/under/clothes, mob/living/user)
UnregisterSignal(user, COMSIG_LIVING_SLAM_TABLE)
- user.bubble_icon = initial(user.bubble_icon)
/obj/item/clothing/accessory/lawyers_badge/proc/table_slam(mob/living/source, obj/structure/table/the_table)
SIGNAL_HANDLER
diff --git a/code/modules/events/meteors/dark_matteor_event.dm b/code/modules/events/meteors/dark_matteor_event.dm
index 412354b16f13d..a2352a1927e01 100644
--- a/code/modules/events/meteors/dark_matteor_event.dm
+++ b/code/modules/events/meteors/dark_matteor_event.dm
@@ -22,7 +22,7 @@
target = potential_target
break
//if target was never chosen the target is null aka the matteor will act as spacedust (and can technically miss)
- spawn_meteor(list(/obj/effect/meteor/dark_matteor = 1), null, target)
+ spawn_meteor(list(/obj/effect/meteor/dark_matteor = 1), null, target, distance_from_edge = 10)
/datum/round_event/dark_matteor/announce(fake)
priority_announce("Warning. Excessive tampering of meteor satellites has attracted a dark matt-eor. Signature approaching [GLOB.station_name]. Please brace for impact.", "Meteor Alert", 'sound/announcer/alarm/airraid.ogg')
diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm
index c5bd80e5d7174..2a08f566347a9 100644
--- a/code/modules/fishing/fish/_fish.dm
+++ b/code/modules/fishing/fish/_fish.dm
@@ -181,7 +181,6 @@
apply_traits() //Make sure traits are applied before size and weight.
update_size_and_weight()
- register_evolutions()
register_context()
register_item_context()
@@ -687,11 +686,6 @@
var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait_type]
trait.apply_to_fish(src)
-/obj/item/fish/proc/register_evolutions()
- for(var/evolution_type in evolution_types)
- var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
- evolution.register_fish(src)
-
/obj/item/fish/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
check_flopping()
@@ -720,17 +714,28 @@
/obj/item/fish/proc/feed(datum/reagents/fed_reagents)
if(status != FISH_ALIVE)
return
- var/fed_reagent_type
+
+ ///If one of the reagent with fish effects is also our food reagent this is set to TRUE
+ var/already_fed = FALSE
+ for(var/datum/reagent/reagent as anything in fed_reagents.reagent_list)
+ if(!fed_reagents.has_reagent(reagent.type, 0.1) || !reagent.used_on_fish(src))
+ continue
+ fed_reagents.remove_reagent(reagent.type, 0.1)
+ if(reagent.type == food)
+ already_fed = TRUE
+
+ if(already_fed)
+ sate_hunger()
+ return
+
if(fed_reagents.remove_reagent(food, 0.1))
- fed_reagent_type = food
sate_hunger()
- else
- var/datum/reagent/wrong_reagent = pick(fed_reagents.reagent_list)
- if(!wrong_reagent)
- return
- fed_reagent_type = wrong_reagent.type
- fed_reagents.remove_reagent(fed_reagent_type, 0.1)
- SEND_SIGNAL(src, COMSIG_FISH_FED, fed_reagents, fed_reagent_type)
+ return
+
+ var/datum/reagent/wrong_reagent = pick(fed_reagents.reagent_list)
+ if(!wrong_reagent)
+ return
+ fed_reagents.remove_reagent(wrong_reagent.type, 0.1)
/**
* Base multiplier of the difference between current size and weight and their maximum value
@@ -761,11 +766,14 @@
hunger_mult = 1 - (hunger - FISH_GROWTH_PEAK) * 4
if(hunger_mult <= 0)
return
+ var/base_mult = FISH_GROWTH_MULT
+ if(HAS_TRAIT(src, TRAIT_FISH_QUICK_GROWTH))
+ base_mult *= 2.5
if(size < maximum_size)
- new_size += CEILING((maximum_size - size) * FISH_GROWTH_MULT / (w_class * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
+ new_size += CEILING((maximum_size - size) * base_mult / (w_class * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
new_size = min(new_size, maximum_size)
if(weight < maximum_weight)
- new_weight += CEILING((maximum_weight - weight) * FISH_GROWTH_MULT / (get_weight_rank() * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
+ new_weight += CEILING((maximum_weight - weight) * base_mult / (get_weight_rank() * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
new_weight = min(new_weight, maximum_weight)
if(new_size != size || new_weight != weight)
update_size_and_weight(new_size, new_weight)
@@ -1014,6 +1022,9 @@
health_change_per_second -= 0.5 //Starving
else
health_change_per_second += 0.5 //Slowly healing
+ if(HAS_TRAIT(src, TRAIT_FISH_ON_TESLIUM))
+ health_change_per_second -= 0.65 //This becomes - 0.15 if safe and not starving.
+
adjust_health(health + health_change_per_second * seconds_per_tick)
/obj/item/fish/proc/adjust_health(amount)
@@ -1223,6 +1234,8 @@
if(istype(loc, /obj/structure/aquarium/bioelec_gen))
fish_zap_range = 5
fish_zap_power = GET_FISH_ELECTROGENESIS(src)
+ if(HAS_TRAIT(src, TRAIT_FISH_ON_TESLIUM))
+ fish_zap_power *= 0.5
fish_zap_flags |= (ZAP_GENERATES_POWER | ZAP_MOB_STUN)
tesla_zap(source = get_turf(src), zap_range = fish_zap_range, power = fish_zap_power, cutoff = 1 MEGA JOULES, zap_flags = fish_zap_flags)
@@ -1308,6 +1321,24 @@
. = ..()
aquarium_vc_color = color || initial(aquarium_vc_color)
+/obj/item/fish/get_infusion_entry()
+ var/amphibious = required_fluid_type == AQUARIUM_FLUID_AIR || HAS_TRAIT(src, TRAIT_FISH_AMPHIBIOUS)
+ var/list/possible_infusions = list()
+ for(var/type in fish_traits)
+ var/datum/fish_trait/trait = GLOB.fish_traits[type]
+ if(!trait.infusion_entry)
+ continue
+ possible_infusions |= trait.infusion_entry
+ if(!length(possible_infusions) && !amphibious)
+ return GLOB.infuser_entries[/datum/infuser_entry/fish]
+ var/datum/infuser_entry/fish/entry = new
+ if(amphibious)
+ entry.output_organs -= /obj/item/organ/internal/lungs/fish
+ for(var/key in possible_infusions)
+ var/datum/infuser_entry/infusion = GLOB.infuser_entries[key]
+ entry.output_organs |= infusion.output_organs
+ return entry
+
/// Returns random fish, using random_case_rarity probabilities.
/proc/random_fish_type(required_fluid)
var/static/probability_table
diff --git a/code/modules/fishing/fish/fish_evolution.dm b/code/modules/fishing/fish/fish_evolution.dm
index 25ce133c98d40..52708add566da 100644
--- a/code/modules/fishing/fish/fish_evolution.dm
+++ b/code/modules/fishing/fish/fish_evolution.dm
@@ -48,6 +48,8 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution)
if(aquarium)
//chances are halved if only one parent has this evolution.
var/real_probability = (mate && (type in mate.evolution_types)) ? probability : probability/2
+ if(HAS_TRAIT(source, TRAIT_FISH_MUTAGENIC) || (mate && HAS_TRAIT(mate, TRAIT_FISH_MUTAGENIC)))
+ real_probability *= 3
if(!prob(real_probability))
return FALSE
if(!ISINRANGE(aquarium.fluid_temp, required_temperature_min, required_temperature_max))
@@ -82,25 +84,12 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution)
. += " [conditions_note]"
return .
-///Proc called to let evolution register signals that are needed for various conditions.
-/datum/fish_evolution/proc/register_fish(obj/item/fish/fish)
- return
-
/datum/fish_evolution/lubefish
probability = 25
new_fish_type = /obj/item/fish/clownfish/lube
new_traits = list(/datum/fish_trait/lubed)
conditions_note = "The fish must be fed lube beforehand."
-/datum/fish_evolution/lubefish/register_fish(obj/item/fish/fish)
- RegisterSignal(fish, COMSIG_FISH_FED, PROC_REF(check_for_lube))
-
-/datum/fish_evolution/lubefish/proc/check_for_lube(obj/item/fish/source, datum/reagents/fed_reagents, wrong_reagent_type)
- SIGNAL_HANDLER
- if((wrong_reagent_type == /datum/reagent/lube) || fed_reagents.remove_reagent(/datum/reagent/lube, 0.1))
- ADD_TRAIT(source, TRAIT_FISH_FED_LUBE, FISH_EVOLUTION)
- addtimer(TRAIT_CALLBACK_REMOVE(source, TRAIT_FISH_FED_LUBE, FISH_EVOLUTION), source.feeding_frequency)
-
/datum/fish_evolution/lubefish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium)
if(!HAS_TRAIT(source, TRAIT_FISH_FED_LUBE))
return FALSE
diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm
index b0eec9d58f29e..2030c1b61585b 100644
--- a/code/modules/fishing/fish/fish_traits.dm
+++ b/code/modules/fishing/fish/fish_traits.dm
@@ -39,6 +39,8 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
var/added_difficulty = 0
/// Reagents to add to the fish whenever the COMSIG_GENERATE_REAGENTS_TO_ADD signal is sent. Their values will be multiplied later.
var/list/reagents_to_add
+ /// If set, the fish may return this infusion entry when get_infusion_entry is called instead of /datum/infuser_entry/fish
+ var/infusion_entry
/// Difficulty modifier from this mod, needs to return a list with two values
/datum/fish_trait/proc/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
@@ -427,6 +429,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
catalog_description = "This fish contains toxins. Feeding it to predatory fishes or people is not recommended."
diff_traits_inheritability = 25
reagents_to_add = list(/datum/reagent/toxin/tetrodotoxin = 1)
+ infusion_entry = /datum/infuser_entry/ttx_healing
///The amount of venom injected if the fish has a stinger is multiplied by this value.
var/venom_mult = 1
@@ -472,6 +475,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
catalog_description = "This fish contains carpotoxin. Definitely not safe for consumption."
diff_traits_inheritability = 50
reagents_to_add = list(/datum/reagent/toxin/carpotoxin = 4)
+ infusion_entry = null
venom_mult = 6
/datum/fish_trait/toxin_immunity
@@ -543,6 +547,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
inheritability = 80
diff_traits_inheritability = 40
catalog_description = "This fish has developed a primitive adaptation to life on both land and water."
+ infusion_entry = /datum/infuser_entry/amphibious
/datum/fish_trait/amphibious/apply_to_fish(obj/item/fish/fish)
. = ..()
@@ -721,6 +726,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
catalog_description = "This fish possess a sac that produces ink."
diff_traits_inheritability = 70
spontaneous_manifest_types = list(/obj/item/fish/squid = 35)
+ infusion_entry = /datum/infuser_entry/squid
/datum/fish_trait/ink/apply_to_fish(obj/item/fish/fish)
. = ..()
diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm
index 23aabcc3ece7c..62d0cdf80a2f7 100644
--- a/code/modules/fishing/fishing_rod.dm
+++ b/code/modules/fishing/fishing_rod.dm
@@ -69,6 +69,11 @@
update_appearance()
+ //Bane effect that make it extra-effective against mobs with water adaptation (read: fish infusion)
+ AddElement(/datum/element/bane, target_type = /mob/living, damage_multiplier = 1.25)
+ RegisterSignal(src, COMSIG_OBJECT_PRE_BANING, PROC_REF(attempt_bane))
+ RegisterSignal(src, COMSIG_OBJECT_ON_BANING, PROC_REF(bane_effects))
+
/obj/item/fishing_rod/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(src == held_item)
if(currently_hooked)
@@ -135,6 +140,19 @@
QDEL_NULL(bait)
update_icon()
+///Fishing rodss should only bane fish DNA-infused spessman
+/obj/item/fishing_rod/proc/attempt_bane(datum/source, mob/living/fish)
+ SIGNAL_HANDLER
+ if(!force || !HAS_TRAIT(fish, TRAIT_WATER_ADAPTATION))
+ return COMPONENT_CANCEL_BANING
+
+///Fishing rods should hard-counter fish DNA-infused spessman
+/obj/item/fishing_rod/proc/bane_effects(datum/source, mob/living/fish)
+ SIGNAL_HANDLER
+ fish.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH, 4 SECONDS)
+ fish.adjust_confusion_up_to(1.5 SECONDS, 3 SECONDS)
+ fish.adjust_wet_stacks(-4)
+
/obj/item/fishing_rod/interact(mob/user)
if(currently_hooked)
reel(user)
diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm
index f182287040082..f6cbd969a8d6d 100644
--- a/code/modules/fishing/sources/source_types.dm
+++ b/code/modules/fishing/sources/source_types.dm
@@ -354,9 +354,6 @@
/datum/fish_source/lavaland/reason_we_cant_fish(obj/item/fishing_rod/rod, mob/fisherman, atom/parent)
. = ..()
- var/turf/approx = get_turf(fisherman) //todo pass the parent
- if(!SSmapping.level_trait(approx.z, ZTRAIT_MINING))
- return "There doesn't seem to be anything to catch here."
if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_REINFORCED))
return "You'll need reinforced fishing line to fish in there"
diff --git a/code/modules/hallucination/on_fire.dm b/code/modules/hallucination/on_fire.dm
index cb4a95dd4420c..7266de17f4669 100644
--- a/code/modules/hallucination/on_fire.dm
+++ b/code/modules/hallucination/on_fire.dm
@@ -37,7 +37,6 @@
return ..()
/datum/hallucination/fire/start()
- hallucinator.set_fire_stacks(max(hallucinator.fire_stacks, 0.1)) //Placebo flammability
fire_overlay = image(fire_icon, hallucinator, fire_icon_state, ABOVE_MOB_LAYER)
hallucinator.client?.images |= fire_overlay
to_chat(hallucinator, span_userdanger("You're set on fire!"))
@@ -47,7 +46,6 @@
return TRUE
/datum/hallucination/fire/Destroy()
- hallucinator.adjust_fire_stacks(-0.1)
hallucinator.clear_alert(ALERT_FIRE, clear_override = TRUE)
hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE)
if(fire_overlay)
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm b/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm
index 6af8c844555da..9bd6436994df1 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm
@@ -151,6 +151,10 @@
inhand_icon_state = null
slowdown = 0
+/obj/item/clothing/suit/chaplainsuit/armor/clock/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/head/helmet/chaplain
name = "crusader helmet"
desc = "Deus Vult."
@@ -179,6 +183,10 @@
inhand_icon_state = null
slowdown = 0
+/obj/item/clothing/suit/chaplainsuit/armor/templar/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/head/helmet/chaplain/cage
name = "cage"
desc = "A cage that restrains the will of the self, allowing one to see the profane world for what it is."
@@ -199,6 +207,10 @@
icon_state = "knight_ancient"
inhand_icon_state = null
+/obj/item/clothing/suit/chaplainsuit/armor/ancient/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/head/helmet/chaplain/witchunter_hat
name = "witchunter hat"
desc = "This hat saw much use back in the day."
@@ -230,6 +242,11 @@
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
armor_type = /datum/armor/armor_crusader
+/obj/item/clothing/suit/chaplainsuit/armor/crusader/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
+
/datum/armor/armor_crusader
melee = 50
bullet = 50
diff --git a/code/modules/library/skill_learning/generic_skillchips/misc.dm b/code/modules/library/skill_learning/generic_skillchips/misc.dm
new file mode 100644
index 0000000000000..aea850b8f49f2
--- /dev/null
+++ b/code/modules/library/skill_learning/generic_skillchips/misc.dm
@@ -0,0 +1,157 @@
+//Contains generic skillchips that are fairly short and simple
+
+/obj/item/skillchip/basketweaving
+ name = "Basketsoft 3000 skillchip"
+ desc = "Underwater edition."
+ auto_traits = list(TRAIT_UNDERWATER_BASKETWEAVING_KNOWLEDGE)
+ skill_name = "Underwater Basketweaving"
+ skill_description = "Master intricate art of using twine to create perfect baskets while submerged."
+ skill_icon = "shopping-basket"
+ activate_message = span_notice("You're one with the twine and the sea.")
+ deactivate_message = span_notice("Higher mysteries of underwater basketweaving leave your mind.")
+
+/obj/item/skillchip/wine_taster
+ name = "WINE skillchip"
+ desc = "Wine.Is.Not.Equal version 5."
+ auto_traits = list(TRAIT_WINE_TASTER)
+ skill_name = "Wine Tasting"
+ skill_description = "Recognize wine vintage from taste alone. Never again lack an opinion when presented with an unknown drink."
+ skill_icon = "wine-bottle"
+ activate_message = span_notice("You recall wine taste.")
+ deactivate_message = span_notice("Your memories of wine evaporate.")
+
+/obj/item/skillchip/bonsai
+ name = "Hedge 3 skillchip"
+ auto_traits = list(TRAIT_BONSAI)
+ skill_name = "Hedgetrimming"
+ skill_description = "Trim hedges and potted plants into marvelous new shapes with any old knife. Not applicable to plastic plants."
+ skill_icon = "spa"
+ activate_message = span_notice("Your mind is filled with plant arrangments.")
+ deactivate_message = span_notice("You can't remember what a hedge looks like anymore.")
+
+/obj/item/skillchip/useless_adapter
+ name = "Skillchip adapter"
+ skill_name = "Useless adapter"
+ skill_description = "Allows you to insert another skillchip into this adapter after it has been inserted into your brain..."
+ skill_icon = "plug"
+ activate_message = span_notice("You can now activate another chip through this adapter, but you're not sure why you did this...")
+ deactivate_message = span_notice("You no longer have the useless skillchip adapter.")
+ skillchip_flags = SKILLCHIP_ALLOWS_MULTIPLE
+ // Literally does nothing.
+ complexity = 0
+ slot_use = 0
+
+/obj/item/skillchip/light_remover
+ name = "N16H7M4R3 skillchip"
+ auto_traits = list(TRAIT_LIGHTBULB_REMOVER)
+ skill_name = "Lightbulb Removing"
+ skill_description = "Stop failing taking out lightbulbs today, no gloves needed!"
+ skill_icon = "lightbulb"
+ activate_message = span_notice("Your feel like your pain receptors are less sensitive to hot objects.")
+ deactivate_message = span_notice("You feel like hot objects could stop you again...")
+
+/obj/item/skillchip/disk_verifier
+ name = "K33P-TH4T-D15K skillchip"
+ auto_traits = list(TRAIT_DISK_VERIFIER)
+ skill_name = "Nuclear Disk Verification"
+ skill_description = "Nuclear authentication disks have an extremely long serial number for verification. This skillchip stores that number, which allows the user to automatically spot forgeries."
+ skill_icon = "save"
+ activate_message = span_notice("You feel your mind automatically verifying long serial numbers on disk shaped objects.")
+ deactivate_message = span_notice("The innate recognition of absurdly long disk-related serial numbers fades from your mind.")
+
+/obj/item/skillchip/entrails_reader
+ name = "3NTR41LS skillchip"
+ auto_traits = list(TRAIT_ENTRAILS_READER)
+ skill_name = "Entrails Reader"
+ skill_description = "Be able to learn about a person's life, by looking at their internal organs. Not to be confused with looking into the future."
+ skill_icon = "lungs"
+ activate_message = span_notice("You feel that you know a lot about interpreting organs.")
+ deactivate_message = span_notice("Knowledge of liver damage, heart strain and lung scars fades from your mind.")
+
+/obj/item/skillchip/appraiser
+ name = "GENUINE ID Appraisal Now! skillchip"
+ auto_traits = list(TRAIT_ID_APPRAISER)
+ skill_name = "ID Appraisal"
+ skill_description = "Appraise an ID and see if it's issued from centcom, or just a cruddy station-printed one."
+ skill_icon = "magnifying-glass"
+ activate_message = span_notice("You feel that you can recognize special, minute details on ID cards.")
+ deactivate_message = span_notice("Was there something special about certain IDs?")
+
+/obj/item/skillchip/sabrage
+ name = "Le S48R4G3 skillchip"
+ auto_traits = list(TRAIT_SABRAGE_PRO)
+ skill_name = "Sabrage Proficiency"
+ skill_description = "Grants the user knowledge of the intricate structure of a champagne bottle's structural weakness at the neck, \
+ improving their proficiency at being a show-off at officer parties."
+ skill_icon = "bottle-droplet"
+ activate_message = span_notice("You feel a new understanding of champagne bottles and methods on how to remove their corks.")
+ deactivate_message = span_notice("The knowledge of the subtle physics residing inside champagne bottles fades from your mind.")
+
+/obj/item/skillchip/brainwashing
+ name = "suspicious skillchip"
+ auto_traits = list(TRAIT_BRAINWASHING)
+ skill_name = "Brainwashing"
+ skill_description = "WARNING: The integrity of this chip is compromised. Please discard this skillchip."
+ skill_icon = "soap"
+ activate_message = span_notice("...But all at once it comes to you... something involving putting a brain in a washing machine?")
+ deactivate_message = span_warning("All knowledge of the secret brainwashing technique is GONE.")
+
+/obj/item/skillchip/brainwashing/examine(mob/user)
+ . = ..()
+ . += span_warning("It seems to have been corroded over time, putting this in your head may not be the best idea...")
+
+/obj/item/skillchip/brainwashing/on_activate(mob/living/carbon/user, silent = FALSE)
+ to_chat(user, span_danger("You get a pounding headache as the chip sends corrupt memories into your head!"))
+ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20)
+ . = ..()
+
+/obj/item/skillchip/chefs_kiss
+ name = "K1SS skillchip"
+ auto_traits = list(TRAIT_CHEF_KISS)
+ skill_name = "Chef's Kiss"
+ skill_description = "Allows you to kiss food you've created to make them with love."
+ skill_icon = "cookie"
+ activate_message = span_notice("You recall learning from your grandmother how they baked their cookies with love.")
+ deactivate_message = span_notice("You forget all memories imparted upon you by your grandmother. Were they even your real grandma?")
+
+/obj/item/skillchip/intj
+ name = "Integrated Intuitive Thinking and Judging skillchip"
+ auto_traits = list(TRAIT_REMOTE_TASTING)
+ skill_name = "Mental Flavour Calculus"
+ skill_description = "When examining food, you can experience the flavours just as well as if you were eating it."
+ skill_icon = FA_ICON_DRUMSTICK_BITE
+ activate_message = span_notice("You think of your favourite food and realise that you can rotate its flavour in your mind.")
+ deactivate_message = span_notice("You feel your food-based mind palace crumbling...")
+
+/obj/item/skillchip/drunken_brawler
+ name = "F0RC3 4DD1CT10N skillchip"
+ auto_traits = list(TRAIT_DRUNKEN_BRAWLER)
+ skill_name = "Drunken Unarmed Proficiency"
+ skill_description = "When intoxicated, you gain increased unarmed effectiveness."
+ skill_icon = "wine-bottle"
+ activate_message = span_notice("You honestly could do with a drink. Never know when someone might try and jump you around here.")
+ deactivate_message = span_notice("You suddenly feel a lot safer going around the station sober... ")
+
+/obj/item/skillchip/master_angler
+ name = "Mast-Angl-Er skillchip"
+ auto_traits = list(TRAIT_REVEAL_FISH, TRAIT_EXAMINE_FISHING_SPOT, TRAIT_EXAMINE_FISH, TRAIT_EXAMINE_DEEPER_FISH)
+ skill_name = "Fisherman's Discernment"
+ skill_description = "Lists fishes when examining a fishing spot, gives a hint of whatever thing's biting the hook and more."
+ skill_icon = "fish"
+ activate_message = span_notice("You feel the knowledge and passion of several sunbaked, seasoned fishermen burn within you.")
+ deactivate_message = span_notice("You no longer feel like casting a fishing rod by the sunny riverside.")
+
+ actions_types = list(/datum/action/cooldown/fishing_tip)
+
+/datum/action/cooldown/fishing_tip
+ name = "Dispense Fishing Tip"
+ desc = "Recall a pearl of wisdom about fishing."
+ button_icon = 'icons/hud/radial_fishing.dmi'
+ button_icon_state = "river"
+ background_icon_state = "bg_default"
+ overlay_icon_state = "bg_default_border"
+ cooldown_time = 2.5 SECONDS //enough time to skim through tips.
+
+/datum/action/cooldown/fishing_tip/Activate(atom/target_atom)
+ . = ..()
+ send_tip_of_the_round(owner, pick(GLOB.fishing_tips), source = "Ancient fishing wisdom")
diff --git a/code/modules/library/skill_learning/generic_skillchips/musical.dm b/code/modules/library/skill_learning/generic_skillchips/musical.dm
new file mode 100644
index 0000000000000..0eea811ffab77
--- /dev/null
+++ b/code/modules/library/skill_learning/generic_skillchips/musical.dm
@@ -0,0 +1,91 @@
+/obj/item/skillchip/musical
+ name = "\improper Old Copy of \"Space Station 13: The Musical\""
+ desc = "An old copy of \"Space Station 13: The Musical\", \
+ ran on the station's 100th anniversary...Or maybe it was the 200th?"
+ skill_name = "Memory of a Musical"
+ skill_description = "Allows you to hit that high note, like those that came a century before us."
+ skill_icon = FA_ICON_MUSIC
+ activate_message = span_notice("You feel like you could \u2669 sing a soooong! \u266B")
+ deactivate_message = span_notice("The musical fades from your mind, leaving you with a sense of nostalgia.")
+ custom_premium_price = PAYCHECK_CREW * 4
+
+/obj/item/skillchip/musical/Initialize(mapload, is_removable)
+ . = ..()
+ name = replacetext(name, "Old", round(CURRENT_STATION_YEAR - pick(50, 100, 150, 200, 250), 5))
+
+/obj/item/skillchip/musical/on_activate(mob/living/carbon/user, silent = FALSE)
+ . = ..()
+ RegisterSignal(user, COMSIG_MOB_SAY, PROC_REF(make_music))
+
+/obj/item/skillchip/musical/on_deactivate(mob/living/carbon/user, silent)
+ . = ..()
+ UnregisterSignal(user, COMSIG_MOB_SAY)
+
+/obj/item/skillchip/musical/proc/make_music(mob/living/carbon/source, list/say_args)
+ SIGNAL_HANDLER
+
+ var/raw_message = say_args[SPEECH_MESSAGE]
+ var/list/words = splittext(raw_message, " ")
+ if(length(words) <= 1)
+ say_args[SPEECH_MODS][MODE_SING] = TRUE
+ return
+ var/last_word = words[length(words)]
+ var/num_chars = length_char(last_word)
+ var/last_vowel = ""
+ // find the last vowel present in the word
+ for(var/i in 1 to num_chars)
+ var/char = copytext_char(last_word, i, i + 1)
+ if(char in VOWELS)
+ last_vowel = char
+
+ // now we'll reshape the final word to make it sound like they're singing it
+ var/final_word = ""
+ var/has_ellipsis = copytext(last_word, -3) == "..."
+ for(var/i in 1 to num_chars)
+ var/char = copytext_char(last_word, i, i + 1)
+ // replacing any final periods with exclamation marks (so long as it's not an ellipsis)
+ if(char == "." && i == num_chars && !has_ellipsis)
+ final_word += "!"
+ // or if it's the vowel we found, we're gonna repeat it a few times (holding the note)
+ else if(char == last_vowel)
+ for(var/j in 1 to 4)
+ final_word += char
+ // if we dragged out the last character of the word, just period it
+ if(i == num_chars)
+ final_word += "."
+ // no special handing otherwise
+ else
+ final_word += char
+
+ if(!has_ellipsis)
+ // adding an extra exclamation mark at the end if there's no period
+ var/last_char = copytext_char(final_word, -1)
+ if(last_char != ".")
+ final_word += "!"
+
+ words[length(words)] = final_word
+ // now we siiiiiiing
+ say_args[SPEECH_MESSAGE] = jointext(words, " ")
+ say_args[SPEECH_MODS][MODE_SING] = TRUE
+
+/obj/item/skillchip/musical/examine(mob/user)
+ . = ..()
+ . += span_tinynoticeital("Huh, looks like it'd fit in a skillchip adapter.")
+
+/obj/item/skillchip/musical/examine_more(mob/user)
+ . = ..()
+ var/list/songs = list()
+ songs += "• \"The Ballad of Space Station 13\""
+ songs += "• \"The Captain's Call\""
+ songs += "• \"A Mime's Lament\""
+ songs += "• \"Banned from Cargo\""
+ songs += "• \"Botany Blues\""
+ songs += "• \"Clown Song\""
+ songs += "• \"Elegy to an Engineer\""
+ songs += "• \"Medical Malpractitioner\""
+ songs += "• \"Security Strike\""
+ songs += "• \"Send for the Shuttle\""
+ songs += "• And one song scratched out..."
+
+ . += span_notice("On the back of the chip, you see a list of songs:")
+ . += span_smallnotice("[jointext(songs, "
")]")
diff --git a/code/modules/library/skill_learning/generic_skillchips/point.dm b/code/modules/library/skill_learning/generic_skillchips/point.dm
index 761a482268952..ba6a2e3e236ec 100644
--- a/code/modules/library/skill_learning/generic_skillchips/point.dm
+++ b/code/modules/library/skill_learning/generic_skillchips/point.dm
@@ -11,25 +11,17 @@
activate_message = span_notice("From \"The Definitive Compendium of Body Language for the Aspiring Leader\", page 164, paragraph 3...")
deactivate_message = span_notice("So, uh, yeah, how do I point at things again?")
- ///The action for changing the pointer color
- var/datum/action/change_pointer_color/action
-
-/obj/item/skillchip/big_pointer/Destroy()
- action = null
- return ..()
+ actions_types = list(/datum/action/change_pointer_color)
/obj/item/skillchip/big_pointer/on_activate(mob/living/carbon/user, silent=FALSE)
. = ..()
RegisterSignal(user, COMSIG_MOVABLE_POINTED, PROC_REF(fancier_pointer))
- if(!action)
- action = new(src)
- action.Grant(user)
/obj/item/skillchip/big_pointer/on_deactivate(mob/living/carbon/user, silent=FALSE)
UnregisterSignal(user, COMSIG_MOVABLE_POINTED)
+ var/datum/action/change_pointer_color/action = locate() in actions
action?.arrow_color = null
action?.arrow_overlay = null
- action?.Remove(user)
return ..()
/obj/item/skillchip/big_pointer/proc/fancier_pointer(mob/living/user, atom/pointed, obj/effect/temp_visual/point/point)
@@ -37,6 +29,7 @@
if(HAS_TRAIT(user, TRAIT_UNKNOWN))
return
point.cut_overlays()
+ var/datum/action/change_pointer_color/action = locate() in actions
if(!action.arrow_color)
point.icon_state = "arrow_large"
return
diff --git a/code/modules/library/skill_learning/generic_skillchips/rod_suplex.dm b/code/modules/library/skill_learning/job_skillchips/research_director.dm
similarity index 100%
rename from code/modules/library/skill_learning/generic_skillchips/rod_suplex.dm
rename to code/modules/library/skill_learning/job_skillchips/research_director.dm
diff --git a/code/modules/library/skill_learning/skillchip.dm b/code/modules/library/skill_learning/skillchip.dm
index 10139585dd9a7..eddaf300e08a9 100644
--- a/code/modules/library/skill_learning/skillchip.dm
+++ b/code/modules/library/skill_learning/skillchip.dm
@@ -48,6 +48,10 @@
. = ..()
removable = is_removable
+///We don't grant actions outside of being activated when implanted
+/obj/item/skillchip/item_action_slot_check(slot, mob/user, datum/action/action)
+ return FALSE
+
/**
* Activates the skillchip, if possible.
*
@@ -150,6 +154,9 @@
active = TRUE
+ for(var/datum/action/action as anything in actions)
+ action.Grant(user)
+
COOLDOWN_START(src, chip_cooldown, cooldown)
/**
@@ -185,6 +192,9 @@
active = FALSE
+ for(var/datum/action/action as anything in actions)
+ action.Remove(user)
+
COOLDOWN_START(src, chip_cooldown, cooldown)
/**
@@ -369,236 +379,3 @@
removable = metadata["removable"]
return active_msg
-
-/obj/item/skillchip/basketweaving
- name = "Basketsoft 3000 skillchip"
- desc = "Underwater edition."
- auto_traits = list(TRAIT_UNDERWATER_BASKETWEAVING_KNOWLEDGE)
- skill_name = "Underwater Basketweaving"
- skill_description = "Master intricate art of using twine to create perfect baskets while submerged."
- skill_icon = "shopping-basket"
- activate_message = span_notice("You're one with the twine and the sea.")
- deactivate_message = span_notice("Higher mysteries of underwater basketweaving leave your mind.")
-
-/obj/item/skillchip/wine_taster
- name = "WINE skillchip"
- desc = "Wine.Is.Not.Equal version 5."
- auto_traits = list(TRAIT_WINE_TASTER)
- skill_name = "Wine Tasting"
- skill_description = "Recognize wine vintage from taste alone. Never again lack an opinion when presented with an unknown drink."
- skill_icon = "wine-bottle"
- activate_message = span_notice("You recall wine taste.")
- deactivate_message = span_notice("Your memories of wine evaporate.")
-
-/obj/item/skillchip/bonsai
- name = "Hedge 3 skillchip"
- auto_traits = list(TRAIT_BONSAI)
- skill_name = "Hedgetrimming"
- skill_description = "Trim hedges and potted plants into marvelous new shapes with any old knife. Not applicable to plastic plants."
- skill_icon = "spa"
- activate_message = span_notice("Your mind is filled with plant arrangments.")
- deactivate_message = span_notice("You can't remember what a hedge looks like anymore.")
-
-/obj/item/skillchip/useless_adapter
- name = "Skillchip adapter"
- skill_name = "Useless adapter"
- skill_description = "Allows you to insert another skillchip into this adapter after it has been inserted into your brain..."
- skill_icon = "plug"
- activate_message = span_notice("You can now activate another chip through this adapter, but you're not sure why you did this...")
- deactivate_message = span_notice("You no longer have the useless skillchip adapter.")
- skillchip_flags = SKILLCHIP_ALLOWS_MULTIPLE
- // Literally does nothing.
- complexity = 0
- slot_use = 0
-
-/obj/item/skillchip/light_remover
- name = "N16H7M4R3 skillchip"
- auto_traits = list(TRAIT_LIGHTBULB_REMOVER)
- skill_name = "Lightbulb Removing"
- skill_description = "Stop failing taking out lightbulbs today, no gloves needed!"
- skill_icon = "lightbulb"
- activate_message = span_notice("Your feel like your pain receptors are less sensitive to hot objects.")
- deactivate_message = span_notice("You feel like hot objects could stop you again...")
-
-/obj/item/skillchip/disk_verifier
- name = "K33P-TH4T-D15K skillchip"
- auto_traits = list(TRAIT_DISK_VERIFIER)
- skill_name = "Nuclear Disk Verification"
- skill_description = "Nuclear authentication disks have an extremely long serial number for verification. This skillchip stores that number, which allows the user to automatically spot forgeries."
- skill_icon = "save"
- activate_message = span_notice("You feel your mind automatically verifying long serial numbers on disk shaped objects.")
- deactivate_message = span_notice("The innate recognition of absurdly long disk-related serial numbers fades from your mind.")
-
-/obj/item/skillchip/entrails_reader
- name = "3NTR41LS skillchip"
- auto_traits = list(TRAIT_ENTRAILS_READER)
- skill_name = "Entrails Reader"
- skill_description = "Be able to learn about a person's life, by looking at their internal organs. Not to be confused with looking into the future."
- skill_icon = "lungs"
- activate_message = span_notice("You feel that you know a lot about interpreting organs.")
- deactivate_message = span_notice("Knowledge of liver damage, heart strain and lung scars fades from your mind.")
-
-/obj/item/skillchip/appraiser
- name = "GENUINE ID Appraisal Now! skillchip"
- auto_traits = list(TRAIT_ID_APPRAISER)
- skill_name = "ID Appraisal"
- skill_description = "Appraise an ID and see if it's issued from centcom, or just a cruddy station-printed one."
- skill_icon = "magnifying-glass"
- activate_message = span_notice("You feel that you can recognize special, minute details on ID cards.")
- deactivate_message = span_notice("Was there something special about certain IDs?")
-
-/obj/item/skillchip/sabrage
- name = "Le S48R4G3 skillchip"
- auto_traits = list(TRAIT_SABRAGE_PRO)
- skill_name = "Sabrage Proficiency"
- skill_description = "Grants the user knowledge of the intricate structure of a champagne bottle's structural weakness at the neck, \
- improving their proficiency at being a show-off at officer parties."
- skill_icon = "bottle-droplet"
- activate_message = span_notice("You feel a new understanding of champagne bottles and methods on how to remove their corks.")
- deactivate_message = span_notice("The knowledge of the subtle physics residing inside champagne bottles fades from your mind.")
-
-/obj/item/skillchip/brainwashing
- name = "suspicious skillchip"
- auto_traits = list(TRAIT_BRAINWASHING)
- skill_name = "Brainwashing"
- skill_description = "WARNING: The integrity of this chip is compromised. Please discard this skillchip."
- skill_icon = "soap"
- activate_message = span_notice("...But all at once it comes to you... something involving putting a brain in a washing machine?")
- deactivate_message = span_warning("All knowledge of the secret brainwashing technique is GONE.")
-
-/obj/item/skillchip/brainwashing/examine(mob/user)
- . = ..()
- . += span_warning("It seems to have been corroded over time, putting this in your head may not be the best idea...")
-
-/obj/item/skillchip/brainwashing/on_activate(mob/living/carbon/user, silent = FALSE)
- to_chat(user, span_danger("You get a pounding headache as the chip sends corrupt memories into your head!"))
- user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20)
- . = ..()
-
-/obj/item/skillchip/chefs_kiss
- name = "K1SS skillchip"
- auto_traits = list(TRAIT_CHEF_KISS)
- skill_name = "Chef's Kiss"
- skill_description = "Allows you to kiss food you've created to make them with love."
- skill_icon = "cookie"
- activate_message = span_notice("You recall learning from your grandmother how they baked their cookies with love.")
- deactivate_message = span_notice("You forget all memories imparted upon you by your grandmother. Were they even your real grandma?")
-
-/obj/item/skillchip/master_angler
- name = "Mast-Angl-Er skillchip"
- auto_traits = list(TRAIT_REVEAL_FISH, TRAIT_EXAMINE_FISHING_SPOT, TRAIT_EXAMINE_FISH, TRAIT_EXAMINE_DEEPER_FISH)
- skill_name = "Fisherman's Discernment"
- skill_description = "Lists fishes when examining a fishing spot, gives a hint of whatever thing's biting the hook and more."
- skill_icon = "fish"
- activate_message = span_notice("You feel the knowledge and passion of several sunbaked, seasoned fishermen burn within you.")
- deactivate_message = span_notice("You no longer feel like casting a fishing rod by the sunny riverside.")
-
-/obj/item/skillchip/intj
- name = "Integrated Intuitive Thinking and Judging skillchip"
- auto_traits = list(TRAIT_REMOTE_TASTING)
- skill_name = "Mental Flavour Calculus"
- skill_description = "When examining food, you can experience the flavours just as well as if you were eating it."
- skill_icon = FA_ICON_DRUMSTICK_BITE
- activate_message = span_notice("You think of your favourite food and realise that you can rotate its flavour in your mind.")
- deactivate_message = span_notice("You feel your food-based mind palace crumbling...")
-
-/obj/item/skillchip/drunken_brawler
- name = "F0RC3 4DD1CT10N skillchip"
- auto_traits = list(TRAIT_DRUNKEN_BRAWLER)
- skill_name = "Drunken Unarmed Proficiency"
- skill_description = "When intoxicated, you gain increased unarmed effectiveness."
- skill_icon = "wine-bottle"
- activate_message = span_notice("You honestly could do with a drink. Never know when someone might try and jump you around here.")
- deactivate_message = span_notice("You suddenly feel a lot safer going around the station sober... ")
-
-/obj/item/skillchip/musical
- name = "\improper Old Copy of \"Space Station 13: The Musical\""
- desc = "An old copy of \"Space Station 13: The Musical\", \
- ran on the station's 100th anniversary...Or maybe it was the 200th?"
- skill_name = "Memory of a Musical"
- skill_description = "Allows you to hit that high note, like those that came a century before us."
- skill_icon = FA_ICON_MUSIC
- activate_message = span_notice("You feel like you could \u2669 sing a soooong! \u266B")
- deactivate_message = span_notice("The musical fades from your mind, leaving you with a sense of nostalgia.")
- custom_premium_price = PAYCHECK_CREW * 4
-
-/obj/item/skillchip/musical/Initialize(mapload, is_removable)
- . = ..()
- name = replacetext(name, "Old", round(CURRENT_STATION_YEAR - pick(50, 100, 150, 200, 250), 5))
-
-/obj/item/skillchip/musical/on_activate(mob/living/carbon/user, silent = FALSE)
- . = ..()
- RegisterSignal(user, COMSIG_MOB_SAY, PROC_REF(make_music))
-
-/obj/item/skillchip/musical/on_deactivate(mob/living/carbon/user, silent)
- . = ..()
- UnregisterSignal(user, COMSIG_MOB_SAY)
-
-/obj/item/skillchip/musical/proc/make_music(mob/living/carbon/source, list/say_args)
- SIGNAL_HANDLER
-
- var/raw_message = say_args[SPEECH_MESSAGE]
- var/list/words = splittext(raw_message, " ")
- if(length(words) <= 1)
- say_args[SPEECH_MODS][MODE_SING] = TRUE
- return
- var/last_word = words[length(words)]
- var/num_chars = length_char(last_word)
- var/last_vowel = ""
- // find the last vowel present in the word
- for(var/i in 1 to num_chars)
- var/char = copytext_char(last_word, i, i + 1)
- if(char in VOWELS)
- last_vowel = char
-
- // now we'll reshape the final word to make it sound like they're singing it
- var/final_word = ""
- var/has_ellipsis = copytext(last_word, -3) == "..."
- for(var/i in 1 to num_chars)
- var/char = copytext_char(last_word, i, i + 1)
- // replacing any final periods with exclamation marks (so long as it's not an ellipsis)
- if(char == "." && i == num_chars && !has_ellipsis)
- final_word += "!"
- // or if it's the vowel we found, we're gonna repeat it a few times (holding the note)
- else if(char == last_vowel)
- for(var/j in 1 to 4)
- final_word += char
- // if we dragged out the last character of the word, just period it
- if(i == num_chars)
- final_word += "."
- // no special handing otherwise
- else
- final_word += char
-
- if(!has_ellipsis)
- // adding an extra exclamation mark at the end if there's no period
- var/last_char = copytext_char(final_word, -1)
- if(last_char != ".")
- final_word += "!"
-
- words[length(words)] = final_word
- // now we siiiiiiing
- say_args[SPEECH_MESSAGE] = jointext(words, " ")
- say_args[SPEECH_MODS][MODE_SING] = TRUE
-
-/obj/item/skillchip/musical/examine(mob/user)
- . = ..()
- . += span_tinynoticeital("Huh, looks like it'd fit in a skillchip adapter.")
-
-/obj/item/skillchip/musical/examine_more(mob/user)
- . = ..()
- var/list/songs = list()
- songs += "• \"The Ballad of Space Station 13\""
- songs += "• \"The Captain's Call\""
- songs += "• \"A Mime's Lament\""
- songs += "• \"Banned from Cargo\""
- songs += "• \"Botany Blues\""
- songs += "• \"Clown Song\""
- songs += "• \"Elegy to an Engineer\""
- songs += "• \"Medical Malpractitioner\""
- songs += "• \"Security Strike\""
- songs += "• \"Send for the Shuttle\""
- songs += "• And one song scratched out..."
-
- . += span_notice("On the back of the chip, you see a list of songs:")
- . += span_smallnotice("[jointext(songs, "
")]")
diff --git a/code/modules/meteors/meteor_spawning.dm b/code/modules/meteors/meteor_spawning.dm
index 83b1c9533c577..17e4746753522 100644
--- a/code/modules/meteors/meteor_spawning.dm
+++ b/code/modules/meteors/meteor_spawning.dm
@@ -4,7 +4,7 @@
for(var/i in 1 to number)
spawn_meteor(meteor_types, direction)
-/proc/spawn_meteor(list/meteor_types, direction, atom/target)
+/proc/spawn_meteor(list/meteor_types, direction, atom/target, distance_from_edge = 0)
if (SSmapping.is_planetary())
stack_trace("Tried to spawn meteors in a map which isn't in space.")
return // We're not going to find any space turfs here
@@ -18,7 +18,7 @@
else
start_side = pick(GLOB.cardinals)
var/start_Z = pick(SSmapping.levels_by_trait(ZTRAIT_STATION))
- picked_start = spaceDebrisStartLoc(start_side, start_Z)
+ picked_start = spaceDebrisStartLoc(start_side, start_Z, distance_from_edge)
if(target)
if(!isturf(target))
target = get_turf(target)
@@ -31,22 +31,22 @@
var/new_meteor = pick_weight(meteor_types)
new new_meteor(picked_start, picked_goal)
-/proc/spaceDebrisStartLoc(start_side, Z)
+/proc/spaceDebrisStartLoc(start_side, Z, distance_from_edge = 0)
var/starty
var/startx
switch(start_side)
if(NORTH)
- starty = world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)
- startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ starty = world.maxy - (TRANSITIONEDGE + MAP_EDGE_PAD) - distance_from_edge
+ startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge))
if(EAST)
- starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
- startx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)
+ starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge))
+ startx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD) - distance_from_edge
if(SOUTH)
- starty = (TRANSITIONEDGE + MAP_EDGE_PAD)
- startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ starty = (TRANSITIONEDGE + MAP_EDGE_PAD) + distance_from_edge
+ startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge))
if(WEST)
- starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
- startx = (TRANSITIONEDGE + MAP_EDGE_PAD)
+ starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD + distance_from_edge))
+ startx = (TRANSITIONEDGE + MAP_EDGE_PAD) + distance_from_edge
. = locate(startx, starty, Z)
/proc/spaceDebrisFinishLoc(startSide, Z)
diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm
index af31d32719f14..cc8cba654dd33 100644
--- a/code/modules/mining/equipment/explorer_gear.dm
+++ b/code/modules/mining/equipment/explorer_gear.dm
@@ -293,6 +293,7 @@
/obj/item/clothing/suit/hooded/cloak/godslayer/Initialize(mapload)
. = ..()
allowed = GLOB.mining_suit_allowed
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
/obj/item/clothing/head/hooded/cloakhood/godslayer
name = "godslayer helm"
diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm
index af1b990a6fb6a..7acc0299f1018 100644
--- a/code/modules/mining/lavaland/tendril_loot.dm
+++ b/code/modules/mining/lavaland/tendril_loot.dm
@@ -735,6 +735,7 @@
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT)
AddComponent(/datum/component/armor_plate, maxamount = 1, upgrade_item = /obj/item/drake_remains, armor_mod = /datum/armor/drake_empowerment, upgrade_prefix = "empowered")
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
/obj/item/clothing/head/hooded/berserker/examine()
. = ..()
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index 4d74e029860a6..17591d93d5221 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -76,7 +76,8 @@ INITIALIZE_IMMEDIATE(/mob/dead)
var/client/C = client
to_chat(C, span_notice("Sending you to [pick]."))
- new /atom/movable/screen/splash(null, null, C)
+ var/atom/movable/screen/splash/S = new(null, null, C)
+ S.Fade(FALSE)
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, SERVER_HOPPER_TRAIT)
sleep(2.9 SECONDS) //let the animation play
diff --git a/code/modules/mob/living/basic/drone/drone_tools.dm b/code/modules/mob/living/basic/drone/drone_tools.dm
index 6f3f79bff506e..b55b438362a9d 100644
--- a/code/modules/mob/living/basic/drone/drone_tools.dm
+++ b/code/modules/mob/living/basic/drone/drone_tools.dm
@@ -3,31 +3,14 @@
desc = "Access your built-in tools."
icon = 'icons/hud/screen_drone.dmi'
icon_state = "tool_storage"
+ storage_type = /datum/storage/drone
item_flags = ABSTRACT
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
/obj/item/storage/drone_tools/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
-
- var/static/list/drone_builtins = list(
- /obj/item/crowbar/drone,
- /obj/item/screwdriver/drone,
- /obj/item/wrench/drone,
- /obj/item/weldingtool/drone,
- /obj/item/wirecutters/drone,
- /obj/item/multitool/drone,
- /obj/item/pipe_dispenser/drone,
- /obj/item/t_scanner/drone,
- /obj/item/analyzer/drone,
- /obj/item/soap/drone,
- )
- atom_storage.max_total_storage = 40
- atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL
- atom_storage.max_slots = 10
- atom_storage.do_rustle = FALSE
- atom_storage.set_holdable(drone_builtins)
+ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
/obj/item/storage/drone_tools/PopulateContents()
var/list/builtintools = list()
@@ -44,8 +27,6 @@
for(var/obj/item/tool as anything in builtintools)
tool.AddComponent(/datum/component/holderloving, src, TRUE)
- ADD_TRAIT(tool, TRAIT_NODROP, REF(src))
-
/obj/item/crowbar/drone
name = "built-in crowbar"
diff --git a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm
index 3c2aae36bc1d0..53f9c618c637b 100644
--- a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm
+++ b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm
@@ -100,13 +100,8 @@
return ..()
/mob/living/basic/bee/death(gibbed)
- if(beehome)
- beehome.bees -= src
- beehome = null
- beegent = null
- if(flags_1 & HOLOGRAM_1 || gibbed)
- return ..()
- spawn_corpse()
+ if(!(flags_1 & HOLOGRAM_1) && !gibbed)
+ spawn_corpse()
return ..()
/// Leave something to remember us by
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index 9744bcbada7e5..dcf00435fba91 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -29,6 +29,8 @@
/obj/item/toy/toy_xeno,
/obj/item/sticker, //funny ~Jimmyl
/obj/item/toy/plush/rouny,
+ /obj/item/hand_item,
+ /obj/item/queen_promotion,
))
/mob/living/carbon/alien/Initialize(mapload)
diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm
index 2350788e81f6d..07e63d2ae3256 100644
--- a/code/modules/mob/living/carbon/carbon_update_icons.dm
+++ b/code/modules/mob/living/carbon/carbon_update_icons.dm
@@ -1,7 +1,6 @@
/mob/living/carbon/update_obscured_slots(obscured_flags)
..()
- if(obscured_flags & (HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT|HIDEMUTWINGS))
- update_body()
+ update_body()
/// Updates features and clothing attached to a specific limb with limb-specific offsets
/mob/living/carbon/proc/update_features(feature_key)
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 43e0e5f856fa6..7b21299485107 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -219,6 +219,10 @@
. += "[t_He] [t_is] flushed and wheezing."
if (bodytemperature < dna.species.bodytemp_cold_damage_limit)
. += "[t_He] [t_is] shivering."
+ if(HAS_TRAIT(src, TRAIT_EVIL))
+ . += "[t_His] eyes radiate with a unfeeling, cold detachment. There is nothing but darkness within [t_his] soul."
+ living_user.add_mood_event("encountered_evil", /datum/mood_event/encountered_evil)
+ living_user.set_jitter_if_lower(15 SECONDS)
if(HAS_TRAIT(user, TRAIT_SPIRITUAL) && mind?.holy_role)
. += "[t_He] [t_has] a holy aura about [t_him]."
diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index c5d91207a4e82..37110b70db546 100644
--- a/code/modules/mob/living/carbon/human/_species.dm
+++ b/code/modules/mob/living/carbon/human/_species.dm
@@ -2002,7 +2002,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
*
* Returns a color string or null.
*/
-/datum/species/proc/get_fixed_hair_color(mob/living/carbon/human/for_mob)
+/datum/species/proc/get_fixed_hair_color(mob/living/carbon/for_mob)
ASSERT(!isnull(for_mob))
switch(hair_color_mode)
if(USE_MUTANT_COLOR)
diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm
index 23b1ad5126694..9970d973a7dba 100644
--- a/code/modules/mob/living/carbon/human/dummy.dm
+++ b/code/modules/mob/living/carbon/human/dummy.dm
@@ -115,7 +115,7 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
target.dna.features["moth_wings"] = get_consistent_feature_entry(SSaccessories.moth_wings_list)
target.dna.features["snout"] = get_consistent_feature_entry(SSaccessories.snouts_list)
target.dna.features["spines"] = get_consistent_feature_entry(SSaccessories.spines_list)
- target.dna.features["tail_cat"] = get_consistent_feature_entry(SSaccessories.tails_list_human) // it's a lie
+ target.dna.features["tail_cat"] = get_consistent_feature_entry(SSaccessories.tails_list_felinid) // it's a lie
target.dna.features["tail_lizard"] = get_consistent_feature_entry(SSaccessories.tails_list_lizard)
target.dna.features["tail_monkey"] = get_consistent_feature_entry(SSaccessories.tails_list_monkey)
target.dna.features["pod_hair"] = get_consistent_feature_entry(SSaccessories.pod_hair_list)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 0b12d7ac46000..f997b2c1dffcf 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -105,7 +105,7 @@
if(HAS_TRAIT(src, TRAIT_UNKNOWN))
to_chat(viewer, span_notice("You can't make out that ID anymore."))
return
- if(get_dist(viewer, src) > ID_EXAMINE_DISTANCE + 1) // leeway
+ if(!isobserver(viewer) && get_dist(viewer, src) > ID_EXAMINE_DISTANCE + 1) // leeway, ignored if the viewer is a ghost
to_chat(viewer, span_notice("You can't make out that ID from here."))
return
diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm
index 61b26f8aff1aa..665caf557be41 100644
--- a/code/modules/mob/living/carbon/human/human_update_icons.dm
+++ b/code/modules/mob/living/carbon/human/human_update_icons.dm
@@ -72,8 +72,7 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_obscured_slots(obscured_flags)
..()
- if(obscured_flags & HIDEFACE)
- sec_hud_set_security_status()
+ sec_hud_set_security_status()
/* --------------------------------------- */
//vvvvvv UPDATE_INV PROCS vvvvvv
diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm
index 215640126fa0f..11fecb60bfcc4 100644
--- a/code/modules/mob/living/carbon/human/species_types/felinid.dm
+++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm
@@ -13,6 +13,7 @@
TRAIT_CATLIKE_GRACE,
TRAIT_HATED_BY_DOGS,
TRAIT_USES_SKINTONES,
+ TRAIT_WATER_HATER,
)
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
species_language_holder = /datum/language_holder/felinid
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 9c2021061da1e..77d1b3cb05c93 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1204,8 +1204,11 @@
var/altered_grab_state = pulledby.grab_state
if((body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS) || get_timed_status_effect_duration(/datum/status_effect/staggered)) && pulledby.grab_state < GRAB_KILL) //If prone, resisting out of a grab is equivalent to 1 grab state higher. won't make the grab state exceed the normal max, however
altered_grab_state++
- var/resist_chance = BASE_GRAB_RESIST_CHANCE /// see defines/combat.dm, this should be baseline 60%
- resist_chance = (resist_chance/altered_grab_state) ///Resist chance divided by the value imparted by your grab state. It isn't until you reach neckgrab that you gain a penalty to escaping a grab.
+ if(HAS_TRAIT(src, TRAIT_GRABRESISTANCE))
+ altered_grab_state--
+ // see defines/combat.dm, this should be baseline 60%
+ // Resist chance divided by the value imparted by your grab state. It isn't until you reach neckgrab that you gain a penalty to escaping a grab.
+ var/resist_chance = altered_grab_state ? (BASE_GRAB_RESIST_CHANCE / altered_grab_state) : 100
if(prob(resist_chance))
visible_message(span_danger("[src] breaks free of [pulledby]'s grip!"), \
span_danger("You break free of [pulledby]'s grip!"), null, null, pulledby)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 1d3562d9dfb08..e53fa80dfdd8c 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -342,9 +342,12 @@
/mob/living/silicon/robot/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
if(same_z_layer || QDELING(src))
return ..()
- cut_overlay(eye_lights)
- SET_PLANE_EXPLICIT(eye_lights, PLANE_TO_TRUE(eye_lights.plane), src)
- add_overlay(eye_lights)
+
+ if(eye_lights)
+ cut_overlay(eye_lights)
+ SET_PLANE_EXPLICIT(eye_lights, PLANE_TO_TRUE(eye_lights.plane), src)
+ add_overlay(eye_lights)
+
return ..()
/mob/living/silicon/robot/proc/self_destruct(mob/usr)
diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm
index aa8c8153e97f5..aa6b9ac3a34c2 100644
--- a/code/modules/movespeed/modifiers/mobs.dm
+++ b/code/modules/movespeed/modifiers/mobs.dm
@@ -175,3 +175,17 @@
/datum/movespeed_modifier/magic_ties
multiplicative_slowdown = 0.5
+
+///movespeed modifier that makes you go faster when wet and lying on the floor once past the fish organ set threshold.
+/datum/movespeed_modifier/fish_flopping
+ blacklisted_movetypes = MOVETYPES_NOT_TOUCHING_GROUND
+ multiplicative_slowdown = - (CRAWLING_ADD_SLOWDOWN * 0.71)
+
+///speed bonus given by the fish tail organ when inside water.
+/datum/movespeed_modifier/fish_on_water
+ blacklisted_movetypes = MOVETYPES_NOT_TOUCHING_GROUND
+ multiplicative_slowdown = - /turf/open/water::slowdown
+
+///speed malus given by the fish organ set when dry
+/datum/movespeed_modifier/fish_waterless
+ multiplicative_slowdown = 0.36
diff --git a/code/modules/power/singularity/dark_matter_singularity.dm b/code/modules/power/singularity/dark_matter_singularity.dm
index 294c2063b1b98..870e298cf97cf 100644
--- a/code/modules/power/singularity/dark_matter_singularity.dm
+++ b/code/modules/power/singularity/dark_matter_singularity.dm
@@ -21,6 +21,9 @@
/obj/singularity/dark_matter/Initialize(mapload, starting_energy)
. = ..()
COOLDOWN_START(src, initial_explosion_immunity, 5 SECONDS)
+ var/datum/component/singularity/resolved_singularity = singularity_component.resolve()
+ resolved_singularity.chance_to_move_to_target = 100
+ addtimer(CALLBACK(src, PROC_REF(normalize_tracking)), 20 SECONDS)
/obj/singularity/dark_matter/examine(mob/user)
. = ..()
@@ -50,4 +53,9 @@
desc = "You managed to make a singularity from dark matter, which makes no sense at all, and then you threw a supermatter into it? Are you fucking insane? Fuck it, praise Lord Singuloth."
consumed_supermatter = TRUE
+///For 20 seconds, the singularity has buffed tracking to ensure it actually makes its way to the station, normalizes after 20 seconds
+/obj/singularity/dark_matter/proc/normalize_tracking()
+ var/datum/component/singularity/resolved_singularity = singularity_component.resolve()
+ resolved_singularity.chance_to_move_to_target = consumed_supermatter ? initial(resolved_singularity.chance_to_move_to_target) + DARK_MATTER_SUPERMATTER_CHANCE_BONUS : initial(resolved_singularity.chance_to_move_to_target)
+
#undef DARK_MATTER_SUPERMATTER_CHANCE_BONUS
diff --git a/code/modules/power/supermatter/supermatter_extra_effects.dm b/code/modules/power/supermatter/supermatter_extra_effects.dm
index 4f35c1abe7219..ffbbf566386dd 100644
--- a/code/modules/power/supermatter/supermatter_extra_effects.dm
+++ b/code/modules/power/supermatter/supermatter_extra_effects.dm
@@ -160,7 +160,7 @@
step_towards(movable_atom,center)
/proc/supermatter_anomaly_gen(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5, has_changed_lifespan = TRUE)
- var/turf/local_turf = pick(orange(anomalyrange, anomalycenter))
+ var/turf/local_turf = pick(RANGE_TURFS(anomalyrange, anomalycenter))
if(!local_turf)
return
switch(type)
diff --git a/code/modules/projectiles/projectile/special/neurotoxin.dm b/code/modules/projectiles/projectile/special/neurotoxin.dm
deleted file mode 100644
index 077b3a275e99e..0000000000000
--- a/code/modules/projectiles/projectile/special/neurotoxin.dm
+++ /dev/null
@@ -1,17 +0,0 @@
-/obj/projectile/neurotoxin
- name = "neurotoxin spit"
- icon_state = "neurotoxin"
- damage = 65
- damage_type = STAMINA
- armor_flag = BIO
- impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin
- armour_penetration = 50
-
-/obj/projectile/neurotoxin/on_hit(atom/target, blocked = 0, pierce_hit)
- if(isalien(target))
- damage = 0
- return ..()
-
-/obj/projectile/neurotoxin/damaging //for ai controlled aliums
- damage = 30
- paralyze = 0 SECONDS
diff --git a/code/modules/projectiles/projectile/special/spit.dm b/code/modules/projectiles/projectile/special/spit.dm
new file mode 100644
index 0000000000000..1bc0d547ae1fc
--- /dev/null
+++ b/code/modules/projectiles/projectile/special/spit.dm
@@ -0,0 +1,58 @@
+/obj/projectile/neurotoxin
+ name = "neurotoxin spit"
+ icon_state = "neurotoxin"
+ damage = 65
+ damage_type = STAMINA
+ armor_flag = BIO
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin
+ armour_penetration = 50
+
+/obj/projectile/neurotoxin/on_hit(atom/target, blocked = 0, pierce_hit)
+ if(isalien(target))
+ damage = 0
+ return ..()
+
+/obj/projectile/neurotoxin/damaging //for ai controlled aliums
+ damage = 30
+ paralyze = 0 SECONDS
+
+/obj/projectile/ink_spit
+ name = "ink spit"
+ icon_state = "ink_spit"
+ damage = 5
+ damage_type = STAMINA
+ armor_flag = BIO
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/ink_spit
+ armour_penetration = 50
+ hitsound = SFX_DESECRATION
+ hitsound_wall = SFX_DESECRATION
+
+/obj/projectile/ink_spit/Initialize(mapload)
+ . = ..()
+ if(isliving(firer))
+ var/mob/living/living = firer
+ var/datum/status_effect/organ_set_bonus/fish/bonus = living?.has_status_effect(/datum/status_effect/organ_set_bonus/fish)
+ if(bonus?.bonus_active)
+ damage = 12
+ armour_penetration = 65
+
+ AddComponent(/datum/component/splat, \
+ memory_type = /datum/memory/witnessed_inking, \
+ smudge_type = /obj/effect/decal/cleanable/food/squid_ink, \
+ moodlet_type = /datum/mood_event/inked, \
+ splat_color = COLOR_NEARLY_ALL_BLACK, \
+ hit_callback = CALLBACK(src, PROC_REF(blind_em)), \
+ )
+
+/obj/projectile/ink_spit/proc/blind_em(mob/living/victim, can_splat_on)
+ if(!can_splat_on)
+ return
+ var/powered_up = FALSE
+ if(isliving(firer))
+ var/mob/living/living = firer
+ var/datum/status_effect/organ_set_bonus/fish/bonus = living?.has_status_effect(/datum/status_effect/organ_set_bonus/fish)
+ powered_up = bonus?.bonus_active
+ victim.adjust_temp_blindness_up_to((powered_up ? 6.5 : 4.5) SECONDS, 10 SECONDS)
+ victim.adjust_confusion_up_to((powered_up ? 3 : 1.5) SECONDS, 6 SECONDS)
+ if(powered_up)
+ victim.Knockdown(2 SECONDS) //splat!
diff --git a/code/modules/reagents/chemistry/equilibrium.dm b/code/modules/reagents/chemistry/equilibrium.dm
index 6b43e441d483c..bc7cbd37f4854 100644
--- a/code/modules/reagents/chemistry/equilibrium.dm
+++ b/code/modules/reagents/chemistry/equilibrium.dm
@@ -381,7 +381,7 @@
holder.adjust_thermal_energy(heat_energy * SPECIFIC_HEAT_DEFAULT, 0, CHEMICAL_MAXIMUM_TEMPERATURE)
//Give a chance of sounds
- if(prob(5))
+ if(prob(5) && !HAS_TRAIT(holder.my_atom, TRAIT_SILENT_REACTIONS))
holder.my_atom.audible_message(span_notice("[icon2html(holder.my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [reaction.mix_message]"))
if(reaction.mix_sound)
playsound(get_turf(holder.my_atom), reaction.mix_sound, 80, TRUE)
diff --git a/code/modules/reagents/chemistry/holder/mob_life.dm b/code/modules/reagents/chemistry/holder/mob_life.dm
index 611de150920e4..890797247716b 100644
--- a/code/modules/reagents/chemistry/holder/mob_life.dm
+++ b/code/modules/reagents/chemistry/holder/mob_life.dm
@@ -40,7 +40,7 @@
if(belly)
amount += belly.reagents.get_reagent_amount(toxin.type)
- if(amount <= liver_tolerance)
+ if(amount <= liver_tolerance * toxin.liver_tolerance_multiplier)
owner.reagents.remove_reagent(toxin.type, toxin.metabolization_rate * owner.metabolism_efficiency * seconds_per_tick)
continue
diff --git a/code/modules/reagents/chemistry/holder/reactions.dm b/code/modules/reagents/chemistry/holder/reactions.dm
index 3fbcb57a43424..10c34864dd90c 100644
--- a/code/modules/reagents/chemistry/holder/reactions.dm
+++ b/code/modules/reagents/chemistry/holder/reactions.dm
@@ -183,7 +183,7 @@
if(num_reactions)
SEND_SIGNAL(src, COMSIG_REAGENTS_REACTION_STEP, num_reactions, seconds_per_tick)
- if(length(mix_message)) //This is only at the end
+ if(length(mix_message) && !HAS_TRAIT(my_atom, TRAIT_SILENT_REACTIONS)) //This is only at the end
my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]"))
if(!LAZYLEN(reaction_list))
@@ -210,9 +210,12 @@
stack_trace("The equilibrium datum currently processing in this reagents datum had a desynced holder to the ending reaction. src holder:[my_atom] | equilibrium holder:[equilibrium.holder.my_atom] || src type:[my_atom.type] | equilibrium holder:[equilibrium.holder.my_atom.type]")
LAZYREMOVE(reaction_list, equilibrium)
- var/reaction_message = equilibrium.reaction.mix_message
- if(equilibrium.reaction.mix_sound)
- playsound(get_turf(my_atom), equilibrium.reaction.mix_sound, 80, TRUE)
+ var/reaction_message = null
+
+ if (!HAS_TRAIT(my_atom, TRAIT_SILENT_REACTIONS))
+ reaction_message = equilibrium.reaction.mix_message
+ if(equilibrium.reaction.mix_sound)
+ playsound(get_turf(my_atom), equilibrium.reaction.mix_sound, 80, TRUE)
qdel(equilibrium)
update_total()
SEND_SIGNAL(src, COMSIG_REAGENTS_REACTED, .)
@@ -256,7 +259,7 @@
if(result == reagent.type)
mix_message += end_reaction(equilibrium)
any_stopped = TRUE
- if(length(mix_message))
+ if(length(mix_message) && !HAS_TRAIT(my_atom, TRAIT_SILENT_REACTIONS))
my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))][mix_message.Join()]"))
return any_stopped
@@ -326,7 +329,7 @@
var/list/seen = viewers(4, get_turf(my_atom))
var/iconhtml = icon2html(cached_my_atom, seen)
if(cached_my_atom)
- if(!ismob(cached_my_atom)) // No bubbling mobs
+ if(!ismob(cached_my_atom) && !HAS_TRAIT(my_atom, TRAIT_SILENT_REACTIONS)) // No bubbling mobs
if(selected_reaction.mix_sound)
playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, TRUE)
my_atom.audible_message(span_notice("[iconhtml] [selected_reaction.mix_message]"))
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 8edfb38abc397..87c509bd0a1ed 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -78,6 +78,8 @@
var/list/metabolized_traits
/// A list of traits to apply while the reagent is in a mob.
var/list/added_traits
+ /// Multiplier of the amount purged by reagents such as calomel, multiver, syniver etc.
+ var/purge_multiplier = 1
///The default reagent container for the reagent, used for icon generation
var/obj/item/reagent_containers/default_container = /obj/item/reagent_containers/cup/bottle
@@ -121,8 +123,8 @@
SHOULD_CALL_PARENT(TRUE)
. = SEND_SIGNAL(src, COMSIG_REAGENT_EXPOSE_MOB, exposed_mob, methods, reac_volume, show_message, touch_protection)
- if((methods & penetrates_skin) && exposed_mob.reagents) //smoke, foam, spray
- var/amount = round(reac_volume*clamp((1 - touch_protection), 0, 1), 0.1)
+ if(penetrates_skin & methods) // models things like vapors which penetrate the skin
+ var/amount = round(reac_volume * clamp((1 - touch_protection), 0, 1), 0.1)
if(amount >= 0.5)
exposed_mob.reagents.add_reagent(type, amount, added_purity = purity)
@@ -282,6 +284,10 @@ Primarily used in reagents/reaction_agents
purity = src.purity
return min(1-inverse_chem_val + purity + 0.01, 1) //Gives inverse reactions a 1% purity threshold for being 100% pure to appease players with OCD.
+///Called when feeding a fish. If TRUE is returned, a portion of reagent will be consumed.
+/datum/reagent/proc/used_on_fish(obj/item/fish/fish)
+ return FALSE
+
/**
* Input a reagent_list, outputs pretty readable text!
* Default output will be formatted as
diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
index 8c0aa36189f99..a81dd522b595b 100644
--- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
@@ -388,16 +388,13 @@
var/need_mob_update
need_mob_update = affected_mob.adjustToxLoss(-0.5 * min(medibonus, 3 * normalise_creation_purity()) * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) //not great at healing but if you have nothing else it will work
need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) //kills at 40u
- for(var/r2 in affected_mob.reagents.reagent_list)
- var/datum/reagent/the_reagent2 = r2
- if(the_reagent2 == src)
- continue
- var/amount2purge = 3
- if(holder.has_reagent(/datum/reagent/toxin/anacea))
- amount2purge = 0
- if(medibonus >= 3 && istype(the_reagent2, /datum/reagent/medicine)) //3 unique meds (2+multiver) | (1 + pure multiver) will make it not purge medicines
- continue
- affected_mob.reagents.remove_reagent(the_reagent2.type, amount2purge * REM * seconds_per_tick)
+ if(!holder.has_reagent(/datum/reagent/toxin/anacea))
+ for(var/datum/reagent/second_reagent as anything in affected_mob.reagents.reagent_list)
+ if(second_reagent == src)
+ continue
+ if(medibonus >= 3 && istype(second_reagent, /datum/reagent/medicine)) //3 unique meds (2+multiver) | (1 + pure multiver) will make it not purge medicines
+ continue
+ affected_mob.reagents.remove_reagent(second_reagent.type, 3 * second_reagent.purge_multiplier * REM * seconds_per_tick)
if(need_mob_update)
return UPDATE_MOB_HEALTH
@@ -468,10 +465,10 @@
var/need_mob_update
need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.1 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)
need_mob_update += affected_mob.adjustToxLoss(-1.5 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_biotype = affected_biotype)
- for(var/datum/reagent/R in affected_mob.reagents.reagent_list)
- if(issyrinormusc(R))
+ for(var/datum/reagent/reagent as anything in affected_mob.reagents.reagent_list)
+ if(issyrinormusc(reagent))
continue
- affected_mob.reagents.remove_reagent(R.type, 0.2 * REM * seconds_per_tick)
+ affected_mob.reagents.remove_reagent(reagent.type, 0.2 * reagent.purge_multiplier * REM * seconds_per_tick)
if(need_mob_update)
return UPDATE_MOB_HEALTH
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 39fcf59eae827..09cda685ddaa0 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -432,18 +432,17 @@
/datum/reagent/medicine/calomel/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
- for(var/datum/reagent/target_reagent in affected_mob.reagents.reagent_list)
+ for(var/datum/reagent/target_reagent as anything in affected_mob.reagents.reagent_list)
if(istype(target_reagent, /datum/reagent/medicine/calomel))
continue
- affected_mob.reagents.remove_reagent(target_reagent.type, 3 * REM * seconds_per_tick)
+ affected_mob.reagents.remove_reagent(target_reagent.type, 3 * target_reagent.purge_multiplier * REM * seconds_per_tick)
var/toxin_amount = round(affected_mob.health / 40, 0.1)
if(affected_mob.adjustToxLoss(toxin_amount * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype))
return UPDATE_MOB_HEALTH
/datum/reagent/medicine/calomel/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
. = ..()
- for(var/datum/reagent/medicine/calomel/target_reagent in affected_mob.reagents.reagent_list)
- affected_mob.reagents.remove_reagent(target_reagent.type, 2 * REM * seconds_per_tick)
+ affected_mob.reagents.remove_reagent(type, 2 * REM * seconds_per_tick)
if(affected_mob.adjustToxLoss(2.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype))
return UPDATE_MOB_HEALTH
@@ -467,7 +466,7 @@
var/toxin_chem_amount = 0
for(var/datum/reagent/toxin/target_reagent in affected_mob.reagents.reagent_list)
toxin_chem_amount += 1
- affected_mob.reagents.remove_reagent(target_reagent.type, 5 * REM * seconds_per_tick)
+ affected_mob.reagents.remove_reagent(target_reagent.type, 5 * target_reagent.purge_multiplier * REM * seconds_per_tick)
var/toxin_amount = round(affected_mob.getBruteLoss() / 15, 0.1) + round(affected_mob.getFireLoss() / 30, 0.1) - 3
if(affected_mob.adjustToxLoss(toxin_amount * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype))
. = UPDATE_MOB_HEALTH
@@ -512,9 +511,9 @@
. = ..()
if(affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype))
. = UPDATE_MOB_HEALTH
- for(var/datum/reagent/R in affected_mob.reagents.reagent_list)
- if(R != src)
- affected_mob.reagents.remove_reagent(R.type, 2 * REM * seconds_per_tick)
+ for(var/datum/reagent/reagent as anything in affected_mob.reagents.reagent_list)
+ if(reagent != src)
+ affected_mob.reagents.remove_reagent(reagent.type, 2 * reagent.purge_multiplier * REM * seconds_per_tick)
/datum/reagent/medicine/sal_acid
name = "Salicylic Acid"
@@ -1036,7 +1035,7 @@
else
tips = world.file2list("strings/chemistrytips.txt")
var/message = pick(tips)
- send_tip_of_the_round(affected_mob, message)
+ send_tip_of_the_round(affected_mob, message, source = "Chemical-induced wisdom")
/datum/reagent/medicine/neurine
name = "Neurine"
@@ -1339,8 +1338,8 @@
/datum/reagent/medicine/haloperidol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
- for(var/datum/reagent/drug/R in affected_mob.reagents.reagent_list)
- affected_mob.reagents.remove_reagent(R.type, 5 * REM * seconds_per_tick)
+ for(var/datum/reagent/drug/reagent in affected_mob.reagents.reagent_list)
+ affected_mob.reagents.remove_reagent(reagent.type, 5 * reagent.purge_multiplier * REM * seconds_per_tick)
affected_mob.adjust_drowsiness(4 SECONDS * REM * seconds_per_tick)
if(affected_mob.get_timed_status_effect_duration(/datum/status_effect/jitter) >= 6 SECONDS)
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 53f22def4677d..fd3fac2a2a891 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -277,7 +277,7 @@
if(methods & VAPOR)
exposed_mob.adjust_wet_stacks(reac_volume * WATER_TO_WET_STACKS_FACTOR_VAPOR) // Spraying someone with water with the hope to put them out is just simply too funny to me not to add it.
- if(!isfelinid(exposed_mob))
+ if(!HAS_TRAIT(exposed_mob, TRAIT_WATER_HATER) || HAS_TRAIT(exposed_mob, TRAIT_WATER_ADAPTATION))
return
exposed_mob.incapacitate(1) // startles the felinid, canceling any do_after
@@ -290,9 +290,18 @@
/datum/reagent/water/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
+ var/water_adaptation = HAS_TRAIT(affected_mob, TRAIT_WATER_ADAPTATION)
if(affected_mob.blood_volume)
- affected_mob.blood_volume += 0.1 * REM * seconds_per_tick // water is good for you!
- affected_mob.adjust_drunk_effect(-0.25 * REM * seconds_per_tick) // and even sobers you up slowly!!
+ var/blood_restored = water_adaptation ? 0.3 : 0.1
+ affected_mob.blood_volume += blood_restored * REM * seconds_per_tick // water is good for you!
+ var/drunkness_restored = water_adaptation ? -0.5 : -0.25
+ affected_mob.adjust_drunk_effect(drunkness_restored * REM * seconds_per_tick) // and even sobers you up slowly!!
+ if(water_adaptation)
+ var/need_mob_update = FALSE
+ need_mob_update = affected_mob.adjustToxLoss(-0.2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)
+ need_mob_update += affected_mob.adjustFireLoss(-0.2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)
+ need_mob_update += affected_mob.adjustBruteLoss(-0.2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)
+ return need_mob_update ? UPDATE_MOB_HEALTH : .
// For weird backwards situations where water manages to get added to trays nutrients, as opposed to being snowflaked away like usual.
/datum/reagent/water/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user)
@@ -391,6 +400,7 @@
data["deciseconds_metabolized"] += (seconds_per_tick * 1 SECONDS * REM)
affected_mob.adjust_jitter_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS)
+ var/need_mob_update = FALSE
if(IS_CULTIST(affected_mob))
for(var/datum/action/innate/cult/blood_magic/BM in affected_mob.actions)
@@ -411,14 +421,23 @@
affected_mob.Unconscious(12 SECONDS)
to_chat(affected_mob, span_cult_large("[pick("Your blood is your bond - you are nothing without it", "Do not forget your place", \
"All that power, and you still fail?", "If you cannot scour this poison, I shall scour your meager life!")]."))
+ else if(HAS_TRAIT(affected_mob, TRAIT_EVIL) && SPT_PROB(25, seconds_per_tick)) //Congratulations, your committment to evil has now made holy water a deadly poison to you!
+ if(!IS_CULTIST(affected_mob) || affected_mob.mind?.holy_role != HOLY_ROLE_PRIEST)
+ affected_mob.emote("scream")
+ need_mob_update += affected_mob.adjustFireLoss(3 * REM * seconds_per_tick, updating_health = FALSE)
if(data["deciseconds_metabolized"] >= (1 MINUTES)) // 24 units
if(IS_CULTIST(affected_mob))
affected_mob.mind.remove_antag_datum(/datum/antagonist/cult)
affected_mob.Unconscious(10 SECONDS)
+ else if(HAS_TRAIT(affected_mob, TRAIT_EVIL)) //At this much holy water, you're probably going to fucking melt. good luck
+ if(!IS_CULTIST(affected_mob) || affected_mob.mind?.holy_role != HOLY_ROLE_PRIEST)
+ need_mob_update += affected_mob.adjustFireLoss(10 * REM * seconds_per_tick, updating_health = FALSE)
affected_mob.remove_status_effect(/datum/status_effect/jitter)
affected_mob.remove_status_effect(/datum/status_effect/speech/stutter)
holder?.remove_reagent(type, volume) // maybe this is a little too perfect and a max() cap on the statuses would be better??
+ if(need_mob_update)
+ return UPDATE_MOB_HEALTH
/datum/reagent/water/holywater/expose_turf(turf/exposed_turf, reac_volume)
. = ..()
@@ -569,6 +588,11 @@
if(reac_volume >= 1)
exposed_turf.MakeSlippery(lube_kind, 15 SECONDS, min(reac_volume * 2 SECONDS, 120))
+/datum/reagent/lube/used_on_fish(obj/item/fish/fish)
+ ADD_TRAIT(fish, TRAIT_FISH_FED_LUBE, type) //required for the lubefish mutation
+ addtimer(TRAIT_CALLBACK_REMOVE(fish, TRAIT_FISH_FED_LUBE, type), fish.feeding_frequency, TIMER_UNIQUE|TIMER_OVERRIDE)
+ return TRUE
+
///Stronger kind of lube. Applies TURF_WET_SUPERLUBE.
/datum/reagent/lube/superlube
name = "Super Duper Lube"
@@ -2460,6 +2484,11 @@
affected_mob.update_transform(RESIZE_DEFAULT_SIZE/current_size)
current_size = RESIZE_DEFAULT_SIZE
+/datum/reagent/growthserum/used_on_fish(obj/item/fish/fish)
+ ADD_TRAIT(fish, TRAIT_FISH_QUICK_GROWTH, type)
+ addtimer(TRAIT_CALLBACK_REMOVE(fish, TRAIT_FISH_QUICK_GROWTH, type), fish.feeding_frequency * 0.8, TIMER_UNIQUE|TIMER_OVERRIDE)
+ return TRUE
+
/datum/reagent/plastic_polymers
name = "Plastic Polymers"
description = "the petroleum based components of plastic."
diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
index a804a106f7353..c81eb5e1fb6a5 100644
--- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
@@ -312,6 +312,14 @@
affected_mob.electrocute_act(rand(5, 20), "Teslium in their body", 1, SHOCK_NOGLOVES) //SHOCK_NOGLOVES because it's caused from INSIDE of you
playsound(affected_mob, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+/datum/reagent/teslium/used_on_fish(obj/item/fish/fish)
+ if(HAS_TRAIT_FROM(fish, TRAIT_FISH_ELECTROGENESIS, FISH_TRAIT_DATUM))
+ return FALSE
+ fish.add_traits(list(TRAIT_FISH_ON_TESLIUM, TRAIT_FISH_ELECTROGENESIS), type)
+ addtimer(TRAIT_CALLBACK_REMOVE(fish, TRAIT_FISH_ON_TESLIUM, type), fish.feeding_frequency * 0.75, TIMER_UNIQUE|TIMER_OVERRIDE)
+ addtimer(TRAIT_CALLBACK_REMOVE(fish, TRAIT_FISH_ELECTROGENESIS, type), fish.feeding_frequency * 0.75, TIMER_UNIQUE|TIMER_OVERRIDE)
+ return TRUE
+
/datum/reagent/teslium/on_mob_metabolize(mob/living/carbon/human/affected_mob)
. = ..()
if(!istype(affected_mob))
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 95f73e552be34..39b477a51a572 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -12,6 +12,8 @@
var/toxpwr = 1.5
///The amount to multiply the liver damage this toxin does by (Handled solely in liver code)
var/liver_damage_multiplier = 1
+ ///The multiplier of the liver toxin tolerance, below which any amount toxin will be simply metabolized out with no effect.
+ var/liver_tolerance_multiplier = 1
///won't produce a pain message when processed by liver/life() if there isn't another non-silent toxin present if true
var/silent_toxin = FALSE
///The afflicted must be above this health value in order for the toxin to deal damage
@@ -71,6 +73,11 @@
mytray.mutation_roll(user)
mytray.adjust_toxic(3) //It is still toxic, mind you, but not to the same degree.
+/datum/reagent/mutagen/used_on_fish(obj/item/fish/fish)
+ ADD_TRAIT(fish, TRAIT_FISH_MUTAGENIC, type)
+ addtimer(TRAIT_CALLBACK_REMOVE(fish, TRAIT_FISH_MUTAGENIC, type), fish.feeding_frequency * 0.8, TIMER_UNIQUE|TIMER_OVERRIDE)
+ return TRUE
+
#define LIQUID_PLASMA_BP (50+T0C)
#define LIQUID_PLASMA_IG (325+T0C)
@@ -333,7 +340,7 @@
/datum/reagent/toxin/mindbreaker/fish/on_new(data)
. = ..()
if(holder?.my_atom)
- RegisterSignals(holder.my_atom, list(COMSIG_ITEM_FRIED, TRAIT_FOOD_BBQ_GRILLED), PROC_REF(on_atom_cooked))
+ RegisterSignals(holder.my_atom, list(COMSIG_ITEM_FRIED, COMSIG_ITEM_BARBEQUE_GRILLED), PROC_REF(on_atom_cooked))
/datum/reagent/toxin/mindbreaker/fish/proc/on_atom_cooked(datum/source, cooking_time)
SIGNAL_HANDLER
@@ -956,9 +963,9 @@
if(prob(50))
constructed_flags |= MOB_VOMIT_STUN
affected_mob.vomit(vomit_flags = constructed_flags, distance = rand(0,4))
- for(var/datum/reagent/toxin/R in affected_mob.reagents.reagent_list)
- if(R != src)
- affected_mob.reagents.remove_reagent(R.type, 1)
+ for(var/datum/reagent/toxin/reagent in affected_mob.reagents.reagent_list)
+ if(reagent != src)
+ affected_mob.reagents.remove_reagent(reagent.type, 1 * reagent.purge_multiplier * REM * seconds_per_tick)
/datum/reagent/toxin/spewium/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -1051,8 +1058,8 @@
if(holder.has_reagent(/datum/reagent/medicine/calomel) || holder.has_reagent(/datum/reagent/medicine/pen_acid))
remove_amt = 0.5
. = ..()
- for(var/datum/reagent/medicine/R in affected_mob.reagents.reagent_list)
- affected_mob.reagents.remove_reagent(R.type, remove_amt * REM * normalise_creation_purity() * seconds_per_tick)
+ for(var/datum/reagent/medicine/reagent in affected_mob.reagents.reagent_list)
+ affected_mob.reagents.remove_reagent(reagent.type, remove_amt * reagent.purge_multiplier * REM * normalise_creation_purity() * seconds_per_tick)
//ACID
@@ -1293,6 +1300,9 @@
reagent_state = SOLID
color = COLOR_VERY_LIGHT_GRAY
metabolization_rate = 0.1 * REAGENTS_METABOLISM
+ liver_tolerance_multiplier = 0.1
+ liver_damage_multiplier = 1.25
+ purge_multiplier = 0.15
toxpwr = 0
taste_mult = 0
chemical_flags = REAGENT_NO_RANDOM_RECIPE|REAGENT_CAN_BE_SYNTHESIZED
@@ -1305,9 +1315,22 @@
/datum/reagent/toxin/tetrodotoxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
+ var/need_mob_update
+ if(HAS_TRAIT(affected_mob, TRAIT_TETRODOTOXIN_HEALING))
+ toxpwr = 0
+ liver_tolerance_multiplier = 0
+ silent_toxin = TRUE
+ remove_paralysis()
+ need_mob_update += affected_mob.adjustOxyLoss(-0.7 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type)
+ need_mob_update = affected_mob.adjustToxLoss(-0.75 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)
+ need_mob_update += affected_mob.adjustBruteLoss(-1.2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)
+ need_mob_update += affected_mob.adjustFireLoss(-1.35 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)
+ return need_mob_update ? UPDATE_MOB_HEALTH : .
+
+ liver_tolerance_multiplier = initial(liver_tolerance_multiplier)
+
//be ready for a cocktail of symptoms, including:
//numbness, nausea, vomit, breath loss, weakness, paralysis and nerve damage/impairment and eventually a heart attack if enough time passes.
- var/need_mob_update
switch(current_cycle)
if(7 to 13)
if(SPT_PROB(20, seconds_per_tick))
@@ -1338,6 +1361,7 @@
if(21 to 29)
toxpwr = 1
need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5)
+ need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.7)
if(SPT_PROB(40, seconds_per_tick))
affected_mob.losebreath += 2 * REM * seconds_per_tick
need_mob_update = TRUE
@@ -1354,13 +1378,14 @@
if(29 to INFINITY)
toxpwr = 1.5
need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, BRAIN_DAMAGE_DEATH)
+ need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 1.4)
affected_mob.set_silence_if_lower(3 SECONDS * REM * seconds_per_tick)
need_mob_update += affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick, updating_stamina = FALSE)
affected_mob.adjust_disgust(2 * REM * seconds_per_tick)
if(SPT_PROB(15, seconds_per_tick))
paralyze_limb(affected_mob)
need_mob_update = TRUE
- if(SPT_PROB(10, seconds_per_tick))
+ if(SPT_PROB(20, seconds_per_tick))
affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS))
if(current_cycle > 38 && !length(traits_not_applied) && SPT_PROB(5, seconds_per_tick) && !affected_mob.undergoing_cardiac_arrest())
@@ -1376,6 +1401,11 @@
var/added_trait = pick_n_take(traits_not_applied)
ADD_TRAIT(affected_mob, added_trait, REF(src))
+/datum/reagent/toxin/tetrodotoxin/on_mob_add(mob/living/affected_mob)
+ . = ..()
+ if(HAS_TRAIT(affected_mob, TRAIT_TETRODOTOXIN_HEALING))
+ liver_tolerance_multiplier = 0
+
/datum/reagent/toxin/tetrodotoxin/on_mob_metabolize(mob/living/affected_mob)
. = ..()
RegisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath))
@@ -1383,6 +1413,9 @@
/datum/reagent/toxin/tetrodotoxin/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
UnregisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath))
+ remove_paralysis(affected_mob)
+
+/datum/reagent/toxin/tetrodotoxin/proc/remove_paralysis(mob/living/affected_mob)
// the initial() proc doesn't work for lists.
var/list/initial_list = list(
TRAIT_PARALYSIS_L_ARM = BODY_ZONE_L_ARM,
@@ -1395,5 +1428,5 @@
/datum/reagent/toxin/tetrodotoxin/proc/block_breath(mob/living/source)
SIGNAL_HANDLER
- if(current_cycle > 28)
+ if(current_cycle > 28 && !HAS_TRAIT(source, TRAIT_TETRODOTOXIN_HEALING))
return COMSIG_CARBON_BLOCK_BREATH
diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
index 69dda419d7cd3..0814834b25a51 100644
--- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
+++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
@@ -178,12 +178,12 @@
ghostie.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS)
ghostie.apply_status_effect(/datum/status_effect/revenant/revealed, 10 SECONDS)
ghostie.adjust_health(50)
- for(var/mob/living/carbon/C in get_hearers_in_view(effective_size,T))
- if(IS_CULTIST(C))
- to_chat(C, span_userdanger("The divine explosion sears you!"))
- C.Paralyze(40)
- C.adjust_fire_stacks(5)
- C.ignite_mob()
+ for(var/mob/living/carbon/evil_motherfucker in get_hearers_in_view(effective_size,T))
+ if(IS_CULTIST(evil_motherfucker) || HAS_TRAIT(evil_motherfucker, TRAIT_EVIL))
+ to_chat(evil_motherfucker, span_userdanger("The divine explosion sears you!"))
+ evil_motherfucker.Paralyze(40)
+ evil_motherfucker.adjust_fire_stacks(5)
+ evil_motherfucker.ignite_mob()
..()
/datum/chemical_reaction/gunpowder
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index e95b5eea82089..c045be74fd37a 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -37,6 +37,8 @@
var/fill_icon_state = null
/// The icon file to take fill icon appearances from
var/fill_icon = 'icons/obj/medical/reagent_fillings.dmi'
+ ///The sound this container makes when picked up, dropped if there is liquid inside.
+ var/reagent_container_liquid_sound = SFX_DEFAULT_LIQUID_SLOSH
/obj/item/reagent_containers/apply_fantasy_bonuses(bonus)
. = ..()
@@ -291,3 +293,13 @@
filling.color = mix_color_from_reagents(reagents.reagent_list)
. += filling
+
+/obj/item/reagent_containers/dropped(mob/user, silent)
+ . = ..()
+ if(reagent_container_liquid_sound && reagents.total_volume > 0)
+ playsound(src, reagent_container_liquid_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
+
+/obj/item/reagent_containers/equipped(mob/user, slot, initial = FALSE)
+ . = ..()
+ if((slot & ITEM_SLOT_HANDS) && reagent_container_liquid_sound && reagents.total_volume > 0)
+ playsound(src, reagent_container_liquid_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm
index b6db2ff2843af..4c137786baed8 100644
--- a/code/modules/reagents/reagent_containers/cups/drinks.dm
+++ b/code/modules/reagents/reagent_containers/cups/drinks.dm
@@ -226,6 +226,7 @@
var/mutable_appearance/cap_overlay
var/flip_chance = 10
custom_price = PAYCHECK_LOWER * 0.8
+ reagent_container_liquid_sound = SFX_PLASTIC_BOTTLE_LIQUID_SLOSH
/obj/item/reagent_containers/cup/glass/waterbottle/Initialize(mapload)
cap_overlay = mutable_appearance(cap_icon, cap_icon_state)
diff --git a/code/modules/religion/honorbound/honorbound_trauma.dm b/code/modules/religion/honorbound/honorbound_trauma.dm
index 6bc0879b12592..ed4ecde1592ba 100644
--- a/code/modules/religion/honorbound/honorbound_trauma.dm
+++ b/code/modules/religion/honorbound/honorbound_trauma.dm
@@ -73,6 +73,8 @@
guilty(attacked_mob, "for blasphemous magicks!")
if(HAS_TRAIT(attacked_mob, TRAIT_CULT_HALO))
guilty(attacked_mob, "for blasphemous worship!")
+ if(HAS_TRAIT(attacked_mob, TRAIT_EVIL))
+ guilty(attacked_mob, "an almost fanatical commitment to EEEEVIL!")
if(attacked_mob.mind)
var/datum/mind/guilty_conscience = attacked_mob.mind
if(guilty_conscience.has_antag_datum(/datum/antagonist/abductor))
diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
index 89baa18720d7b..caaafd056423d 100644
--- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
@@ -150,6 +150,10 @@ Slimecrossing Armor
slowdown = 4
var/hit_reflect_chance = 40
+/obj/item/clothing/suit/armor/heavy/adamantine/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/suit/armor/heavy/adamantine/IsReflect(def_zone)
if((def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) && prob(hit_reflect_chance))
return TRUE
diff --git a/code/modules/surgery/organs/external/tails.dm b/code/modules/surgery/organs/external/tails.dm
index cae83153bdc55..5c7b211db7695 100644
--- a/code/modules/surgery/organs/external/tails.dm
+++ b/code/modules/surgery/organs/external/tails.dm
@@ -158,9 +158,6 @@
wag_flags = WAG_ABLE
-/datum/bodypart_overlay/mutant/tail/get_global_feature_list()
- return SSaccessories.tails_list_human
-
/obj/item/organ/external/tail/cat/get_butt_sprite()
return icon('icons/mob/butts.dmi', BUTT_SPRITE_CAT)
@@ -169,6 +166,9 @@
feature_key = "tail_cat"
color_source = ORGAN_COLOR_HAIR
+/datum/bodypart_overlay/mutant/tail/cat/get_global_feature_list()
+ return SSaccessories.tails_list_felinid
+
/obj/item/organ/external/tail/monkey
name = "monkey tail"
preference = "feature_monkey_tail"
diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm
index a7eb888cc2825..63987b148935e 100644
--- a/code/modules/surgery/organs/organ_movement.dm
+++ b/code/modules/surgery/organs/organ_movement.dm
@@ -240,6 +240,15 @@
color = bodypart_overlay.draw_color // so a pink felinid doesn't drop a gray tail
+ if(greyscale_config)
+ get_greyscale_color_from_draw_color()
+ else
+ color = bodypart_overlay.draw_color // so a pink felinid doesn't drop a gray tail
+
+///Here we define how draw_color from the bodypart overlay sets the greyscale colors of organs that use GAGS
+/obj/item/organ/proc/get_greyscale_color_from_draw_color()
+ color = bodypart_overlay.draw_color //Defaults to the legacy behaviour of applying the color to the item.
+
/// In space station videogame, nothing is sacred. If somehow an organ is removed unexpectedly, handle it properly
/obj/item/organ/proc/forced_removal()
SIGNAL_HANDLER
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 95b7f4a4634d8..38fa7d6a78a41 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -99,6 +99,7 @@
#include "bake_a_cake.dm"
#include "barsigns.dm"
#include "baseturfs.dm"
+#include "bee.dm"
#include "bespoke_id.dm"
#include "binary_insert.dm"
#include "bitrunning.dm"
@@ -118,6 +119,7 @@
#include "circuit_component_category.dm"
#include "client_colours.dm"
#include "closets.dm"
+#include "clothing_drops_items.dm"
#include "clothing_under_armor_subtype_check.dm"
#include "combat.dm"
#include "combat_stamina.dm"
diff --git a/code/modules/unit_tests/bee.dm b/code/modules/unit_tests/bee.dm
new file mode 100644
index 0000000000000..dad3a4d1a7372
--- /dev/null
+++ b/code/modules/unit_tests/bee.dm
@@ -0,0 +1,15 @@
+/// Test beegent transfer
+/datum/unit_test/beegent
+
+/datum/unit_test/beegent/Run()
+ var/mob/living/basic/bee/bee = allocate(__IMPLIED_TYPE__)
+ var/turf/bee_turf = get_turf(bee)
+ var/datum/reagent/picked = GLOB.chemical_reagents_list[/datum/reagent/toxin/fentanyl]
+ bee.assign_reagent(picked)
+ bee.death()
+ var/obj/item/trash/bee/dead_bee = locate() in bee_turf
+ TEST_ASSERT_NOTNULL(dead_bee, "The bee did not leave a corpse.")
+ TEST_ASSERT_EQUAL(dead_bee.beegent, picked, "The bee's corpse did not have the correct beegent assigned.")
+ TEST_ASSERT(dead_bee.reagents.has_reagent(/datum/reagent/toxin/fentanyl), "The bee's corpse did not contain any of the beegent.")
+ // clean up, we aren't allocated
+ QDEL_NULL(dead_bee)
diff --git a/code/modules/unit_tests/clothing_drops_items.dm b/code/modules/unit_tests/clothing_drops_items.dm
new file mode 100644
index 0000000000000..8f1653bf4b6d2
--- /dev/null
+++ b/code/modules/unit_tests/clothing_drops_items.dm
@@ -0,0 +1,53 @@
+/// Tests that removing a piece of clothing drops items that hold said piece of clothing
+/datum/unit_test/clothing_drops_items
+
+/datum/unit_test/clothing_drops_items/Run()
+ test_human()
+ test_android()
+
+/datum/unit_test/clothing_drops_items/proc/test_human()
+ var/list/dummy_items = allocate_items()
+ var/mob/living/carbon/human/consistent/dummy = allocate(__IMPLIED_TYPE__)
+
+ for(var/slot in dummy_items)
+ TEST_ASSERT(dummy.equip_to_slot_if_possible(dummy_items[slot], text2num(slot)), \
+ "[/datum/species/human::name] Dummy failed to equip one of the starting items ([dummy_items[slot]]). Test aborted.")
+
+ dummy.dropItemToGround(dummy.w_uniform)
+
+ for(var/slot in dummy_items)
+ var/obj/item/item = dummy_items[slot]
+ if(item.slot_flags & ITEM_SLOT_ICLOTHING)
+ continue
+ else if(item.slot_flags & (ITEM_SLOT_BACK|ITEM_SLOT_FEET))
+ TEST_ASSERT_EQUAL(item.loc, dummy, "[item] should not have been dropped when unequipping the jumpsuit from \a [/datum/species/human::name].")
+ else
+ TEST_ASSERT_EQUAL(item.loc, dummy.loc, "[item] should have been dropped when unequipping the jumpsuit from \a [/datum/species/human::name].")
+
+/datum/unit_test/clothing_drops_items/proc/test_android()
+ var/list/robo_dummy_items = allocate_items()
+ var/mob/living/carbon/human/consistent/robo_dummy = allocate(__IMPLIED_TYPE__)
+ robo_dummy.set_species(/datum/species/android)
+
+ for(var/slot in robo_dummy_items)
+ TEST_ASSERT(robo_dummy.equip_to_slot_if_possible(robo_dummy_items[slot], text2num(slot)), \
+ "[/datum/species/android::name] Dummy failed to equip one of the starting items ([robo_dummy_items[slot]]). Test aborted.")
+
+ robo_dummy.dropItemToGround(robo_dummy.w_uniform)
+
+ for(var/slot in robo_dummy_items)
+ var/obj/item/item = robo_dummy_items[slot]
+ if(item.slot_flags & ITEM_SLOT_ICLOTHING)
+ continue
+ TEST_ASSERT_EQUAL(item.loc, robo_dummy, "[item] should not have been dropped when unequipping the jumpsuit from \a [/datum/species/android::name].")
+
+/datum/unit_test/clothing_drops_items/proc/allocate_items()
+ return list(
+ "[ITEM_SLOT_ICLOTHING]" = allocate(/obj/item/clothing/under/color/rainbow), // do this one first, it holds everything
+ "[ITEM_SLOT_FEET]" = allocate(/obj/item/clothing/shoes/jackboots),
+ "[ITEM_SLOT_BELT]" = allocate(/obj/item/storage/belt/utility),
+ "[ITEM_SLOT_BACK]" = allocate(/obj/item/storage/backpack),
+ "[ITEM_SLOT_ID]" = allocate(/obj/item/card/id/advanced/gold/captains_spare),
+ "[ITEM_SLOT_RPOCKET]" = allocate(/obj/item/assembly/flash/handheld),
+ "[ITEM_SLOT_LPOCKET]" = allocate(/obj/item/toy/plush/lizard_plushie),
+ )
diff --git a/code/modules/unit_tests/dcs_check_list_arguments.dm b/code/modules/unit_tests/dcs_check_list_arguments.dm
index 67d7417062b27..769574cf95f29 100644
--- a/code/modules/unit_tests/dcs_check_list_arguments.dm
+++ b/code/modules/unit_tests/dcs_check_list_arguments.dm
@@ -11,7 +11,7 @@
*
* Most of the time, you won't encounter two different static lists with similar contents used as element args,
* meaning using static lists is accepted. However, should that happen, it's advised to replace the instances
- * with various string_x procs: lists, assoc_lists, assoc_nested_lists or numbers_list, depending on the type.
+ * with either string_list(), string_assoc_list(), string_assoc_nested_list() or string_numbers_list(), depending on the contents of the list.
*
* In the case of an element where the position of the contents of each datum list argument is important,
* ELEMENT_DONT_SORT_LIST_ARGS should be added to its flags, to prevent such issues where the contents are similar
@@ -51,5 +51,5 @@
TEST_FAIL("Found [length(bad_lists)] datum list arguments with similar contents for [element_type]. Contents: [json_encode(unsorted_list)].")
///Let's avoid sending the same instructions over and over, as it's just going to clutter the CI and confuse someone.
if(we_failed)
- TEST_FAIL("Ensure that each list is static or cached. string_lists() (as well as similar procs) is your friend here.\n\
+ TEST_FAIL("Ensure that each list is static or cached. string_list() (as well as similar procs) is your friend here.\n\
Check the documentation from dcs_check_list_arguments.dm for more information!")
diff --git a/code/modules/unit_tests/organ_set_bonus.dm b/code/modules/unit_tests/organ_set_bonus.dm
index 1231ddd5c6670..25fe7bcd4f59f 100644
--- a/code/modules/unit_tests/organ_set_bonus.dm
+++ b/code/modules/unit_tests/organ_set_bonus.dm
@@ -34,7 +34,9 @@
inserted_organs += organ
// Search for added Status Effect.
- var/datum/status_effect/organ_set_bonus/added_status = locate(/datum/status_effect/organ_set_bonus) in lab_rat.status_effects
+ var/datum/status_effect/organ_set_bonus/added_status
+ if(!infuser_entry.unreachable_effect)
+ added_status = locate(/datum/status_effect/organ_set_bonus) in lab_rat.status_effects
// If threshold_desc is filled-in, it implies the organ_set_bonus Status Effect should be activated.
// Without it, we'll assume there isn't a Status Effect to look for.
diff --git a/code/modules/vending/cola.dm b/code/modules/vending/cola.dm
index d9fddd07f1731..21f61f9a98b28 100644
--- a/code/modules/vending/cola.dm
+++ b/code/modules/vending/cola.dm
@@ -36,6 +36,40 @@
extra_price = PAYCHECK_CREW
payment_department = ACCOUNT_SRV
+ var/static/list/spiking_booze = list(
+ // Your "common" spiking booze
+ /datum/reagent/consumable/ethanol/vodka = 5,
+ /datum/reagent/consumable/ethanol/beer = 5,
+ /datum/reagent/consumable/ethanol/whiskey = 5,
+ /datum/reagent/consumable/ethanol/gin = 5,
+ /datum/reagent/consumable/ethanol/rum = 5,
+ // A bit rarer, can be dangerous if you take too much
+ /datum/reagent/consumable/ethanol/thirteenloko = 3,
+ /datum/reagent/consumable/ethanol/absinthe = 3,
+ /datum/reagent/consumable/ethanol/hooch = 3,
+ /datum/reagent/consumable/ethanol/moonshine = 3,
+ // Gets funky here
+ /datum/reagent/consumable/ethanol/beepsky_smash = 1,
+ /datum/reagent/consumable/ethanol/gargle_blaster = 1,
+ /datum/reagent/consumable/ethanol/neurotoxin = 1,
+ )
+
+/obj/machinery/vending/cola/on_dispense(obj/item/vended_item)
+ // 35% chance that your drink will be safe, as safe pure acid and sugar that these drinks probably are can be
+ if(!onstation || !HAS_TRAIT(SSstation, STATION_TRAIT_SPIKED_DRINKS) || !prob(65))
+ return
+ // Don't fill booze with more booze
+ if (isnull(vended_item.reagents) || vended_item.reagents.has_reagent(/datum/reagent/consumable/ethanol, check_subtypes = TRUE))
+ return
+ var/removed_volume = vended_item.reagents.remove_all(rand(5, vended_item.reagents.maximum_volume * 0.5))
+ if (!removed_volume)
+ return
+ // Don't want bubbling sodas when we add some rum to cola
+ ADD_TRAIT(vended_item, TRAIT_SILENT_REACTIONS, VENDING_MACHINE_TRAIT)
+ vended_item.reagents.add_reagent(pick_weight(spiking_booze), removed_volume)
+ vended_item.reagents.handle_reactions()
+ REMOVE_TRAIT(vended_item, TRAIT_SILENT_REACTIONS, VENDING_MACHINE_TRAIT)
+
/obj/item/vending_refill/cola
machine_name = "Robust Softdrinks"
icon_state = "refill_cola"
diff --git a/html/changelogs/AutoChangeLog-pr-86288.yml b/html/changelogs/AutoChangeLog-pr-86288.yml
new file mode 100644
index 0000000000000..c7418bff97cba
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-86288.yml
@@ -0,0 +1,9 @@
+author: "EuSouAFazer"
+delete-after: True
+changes:
+ - balance: "[TGC] Rebalances nearly every card in the game."
+ - balance: "[TGC] Several keywords had their effects modified."
+ - bugfix: "[TGC] Black Gaia is no longer an \"Artifact\", Fryer no longer mentions tapping it."
+ - spellcheck: "[TGC] Replaced \"Tap this card:\" with just \"Tap:\" alongside other wording improvements"
+ - spellcheck: "[TGC] Mana has been replaced with Plasma. This is a completely cosmetic change."
+ - refactor: "[TGC] Merged many, many redudant card subtypes (the mechanic, not the byond code stuff) into more comprehensive ones."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87099.yml b/html/changelogs/AutoChangeLog-pr-87099.yml
new file mode 100644
index 0000000000000..d865ed02f623a
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87099.yml
@@ -0,0 +1,4 @@
+author: "EEASAS"
+delete-after: True
+changes:
+ - bugfix: "adds plasma to wawastation's xenobio"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87100.yml b/html/changelogs/AutoChangeLog-pr-87100.yml
new file mode 100644
index 0000000000000..6269699631151
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87100.yml
@@ -0,0 +1,4 @@
+author: "Cruix"
+delete-after: True
+changes:
+ - bugfix: "Fixed chameleon clothing sometimes making you bald or hiding other parts of your sprite."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87142.yml b/html/changelogs/AutoChangeLog-pr-87142.yml
new file mode 100644
index 0000000000000..2352bab87d602
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87142.yml
@@ -0,0 +1,4 @@
+author: "SmArtKar"
+delete-after: True
+changes:
+ - qol: "Jetpack movement near walls should be much smoother"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87150.yml b/html/changelogs/AutoChangeLog-pr-87150.yml
new file mode 100644
index 0000000000000..0a18c0a436984
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87150.yml
@@ -0,0 +1,4 @@
+author: "Melbert"
+delete-after: True
+changes:
+ - bugfix: "Dead bees maintain their color and reagents"
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-10.yml b/html/changelogs/archive/2024-10.yml
index a92a43c963c53..a2f1c5e21c4ac 100644
--- a/html/changelogs/archive/2024-10.yml
+++ b/html/changelogs/archive/2024-10.yml
@@ -257,3 +257,66 @@
wonderinghost:
- rscadd: Adds a few lights
- bugfix: removes the darkspot in wawa med
+2024-10-09:
+ Ghommie:
+ - rscadd: 'Added a new infusion to the game: Fish. Its main gimmick revolves around
+ being stronger and slippery when wet while weaker when dry.'
+ - balance: Buffed tetrodotoxin a little against liver tolerance and purging reagents.
+ Iajret:
+ - bugfix: health analyzers will show if your heart is missing once again
+ SmArtKar:
+ - image: Carp infusion now changes how your skin looks to reflect your fishy nature
+ SyncIt21:
+ - bugfix: you can drop/put drone tools back in the toolbox
+ - bugfix: you cannot dump the contents of the drone toolbox
+ necromanceranne:
+ - rscadd: Fundamentally Evil quirk. You might act normal, but you know deep down
+ that you totally don't give a shit about anyone but yourself. Empaths better
+ watch out.
+2024-10-10:
+ Ghommie:
+ - bugfix: The fishing portal generator (fish-porter 3000) now correctly links with
+ lava turfs from lavaland.
+ Jewelry-x:
+ - bugfix: fixed reagents not being applied correctly by venomous mobs
+ - bugfix: dead borgs no longer become invisible upon changing z level
+ SmArtKar:
+ - rscadd: New station trait "Spiked Drinks" that will add booze to most sodas has
+ been added to rotation.
+ StrangeWeirdKitten:
+ - admin: The Law Panel now properly logs and communicates law edits.
+2024-10-11:
+ Absolucy:
+ - bugfix: Allow dot radio prefixes to also work with the tgui-say radio prefix display.
+ DATA-xPUNGED:
+ - bugfix: Xeno queens can once again promote Drones to Praetorians
+ - bugfix: They can also once again do hand emotes
+ - bugfix: Ghosts can now see the details of an ID from any distance
+ - bugfix: Server Hopping should fade your screen into black, as it should.
+ EnterTheJake:
+ - bugfix: Dark matter singularity no longer gets stuck on other z levels when summoned.
+ - bugfix: Space Phase now makes the Heretic Space proof.
+ Ghommie:
+ - rscadd: The fishing skillchip now grants an action that dispenses fishing tips.
+ - bugfix: fixed fish infusion. Whoopsies.
+ - rscadd: Feeding fish certain reagents may have some effects. Mutagen for increased
+ evolution probability. Growth serum for faster growth. Teslium for electrogenesis.
+ KazooBard:
+ - rscadd: More cardboard cutout icons (Pirate, ninja, changeling, heretic, abductor)
+ Melbert:
+ - bugfix: Fixed medipens injecting 2x their contents
+ - bugfix: Fixes stuff staying on your body after removing your clothes
+ Qwertytoforty:
+ - bugfix: Anomalies no longer spawn in objects or mobs from the supermatter.
+ grungussuss:
+ - sound: plate armor now makes sound when moving around in it
+ - sound: reagent containers now make sounds if there is liquid in them when picked
+ up or dropped
+ - sound: fire extinguishers now make sounds when dropped or picked up
+ - bugfix: removed a few var edits from maps
+ junkgle01:
+ - image: resprited surplus limbs
+ unit0016:
+ - bugfix: Shuttles that land next to plasma turfs no longer ruin the mass hallucination
+ that is Icebox having Plasma and not just super-deadly; spiky basalt deltas.
+ You're welcome; unreality fans.
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 28fc7bd210b6f..278edffb855e8 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index 7ee26d58ae417..e0206b9753323 100644
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/icons/mob/augmentation/surplus_augments.dmi b/icons/mob/augmentation/surplus_augments.dmi
index 0fafab0533694..82e83ec27d76b 100644
Binary files a/icons/mob/augmentation/surplus_augments.dmi and b/icons/mob/augmentation/surplus_augments.dmi differ
diff --git a/icons/mob/effects/talk.dmi b/icons/mob/effects/talk.dmi
index c6281b29cc48b..5efb7328971a4 100644
Binary files a/icons/mob/effects/talk.dmi and b/icons/mob/effects/talk.dmi differ
diff --git a/icons/mob/human/fish_features.dmi b/icons/mob/human/fish_features.dmi
new file mode 100644
index 0000000000000..185b89d88fc1d
Binary files /dev/null and b/icons/mob/human/fish_features.dmi differ
diff --git a/icons/mob/human/species/misc/bodypart_overlay_simple.dmi b/icons/mob/human/species/misc/bodypart_overlay_simple.dmi
index dbaaeec55cd24..84dd6c4a39e2b 100644
Binary files a/icons/mob/human/species/misc/bodypart_overlay_simple.dmi and b/icons/mob/human/species/misc/bodypart_overlay_simple.dmi differ
diff --git a/icons/mob/human/textures.dmi b/icons/mob/human/textures.dmi
index c5f420c7de866..4408c3e067281 100644
Binary files a/icons/mob/human/textures.dmi and b/icons/mob/human/textures.dmi differ
diff --git a/icons/obj/medical/organs/infuser_organs.dmi b/icons/obj/medical/organs/infuser_organs.dmi
index c2551b41f6668..57c719dbbbb35 100644
Binary files a/icons/obj/medical/organs/infuser_organs.dmi and b/icons/obj/medical/organs/infuser_organs.dmi differ
diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi
index cbf79a92b538f..911ef3e44e425 100644
Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ
diff --git a/sound/items/handling/armor_rustle/plate_armor/attribution.txt b/sound/items/handling/armor_rustle/plate_armor/attribution.txt
new file mode 100644
index 0000000000000..681f995687fb3
--- /dev/null
+++ b/sound/items/handling/armor_rustle/plate_armor/attribution.txt
@@ -0,0 +1,2 @@
+plate armor rustle:
+armor.wav by juryduty -- https://freesound.org/s/180231/ -- License: Creative Commons 0
diff --git a/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle1.ogg b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle1.ogg
new file mode 100644
index 0000000000000..0d5d521ad5b1b
Binary files /dev/null and b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle1.ogg differ
diff --git a/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle2.ogg b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle2.ogg
new file mode 100644
index 0000000000000..dbbf25bbc3658
Binary files /dev/null and b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle2.ogg differ
diff --git a/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle3.ogg b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle3.ogg
new file mode 100644
index 0000000000000..074e1c61197b6
Binary files /dev/null and b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle3.ogg differ
diff --git a/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle4.ogg b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle4.ogg
new file mode 100644
index 0000000000000..6a105b85ac525
Binary files /dev/null and b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle4.ogg differ
diff --git a/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle5.ogg b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle5.ogg
new file mode 100644
index 0000000000000..b2069180364e1
Binary files /dev/null and b/sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle5.ogg differ
diff --git a/sound/items/handling/reagent_containers/default/attribution.txt b/sound/items/handling/reagent_containers/default/attribution.txt
new file mode 100644
index 0000000000000..102c46ad5919c
--- /dev/null
+++ b/sound/items/handling/reagent_containers/default/attribution.txt
@@ -0,0 +1,2 @@
+default_liquid_slosh:
+Liquid bottle shaking long.mp3 by Hope-Sounds -- https://freesound.org/s/502668/ -- License: Creative Commons 0
diff --git a/sound/items/handling/reagent_containers/default/default_liquid_slosh1.ogg b/sound/items/handling/reagent_containers/default/default_liquid_slosh1.ogg
new file mode 100644
index 0000000000000..2177effd93077
Binary files /dev/null and b/sound/items/handling/reagent_containers/default/default_liquid_slosh1.ogg differ
diff --git a/sound/items/handling/reagent_containers/default/default_liquid_slosh2.ogg b/sound/items/handling/reagent_containers/default/default_liquid_slosh2.ogg
new file mode 100644
index 0000000000000..a641635ff0c07
Binary files /dev/null and b/sound/items/handling/reagent_containers/default/default_liquid_slosh2.ogg differ
diff --git a/sound/items/handling/reagent_containers/default/default_liquid_slosh3.ogg b/sound/items/handling/reagent_containers/default/default_liquid_slosh3.ogg
new file mode 100644
index 0000000000000..89eecf36337ad
Binary files /dev/null and b/sound/items/handling/reagent_containers/default/default_liquid_slosh3.ogg differ
diff --git a/sound/items/handling/reagent_containers/default/default_liquid_slosh4.ogg b/sound/items/handling/reagent_containers/default/default_liquid_slosh4.ogg
new file mode 100644
index 0000000000000..feb7c0d29702e
Binary files /dev/null and b/sound/items/handling/reagent_containers/default/default_liquid_slosh4.ogg differ
diff --git a/sound/items/handling/reagent_containers/default/default_liquid_slosh5.ogg b/sound/items/handling/reagent_containers/default/default_liquid_slosh5.ogg
new file mode 100644
index 0000000000000..b2ba3ee73c99b
Binary files /dev/null and b/sound/items/handling/reagent_containers/default/default_liquid_slosh5.ogg differ
diff --git a/sound/items/handling/reagent_containers/plastic_bottle/attribution.txt b/sound/items/handling/reagent_containers/plastic_bottle/attribution.txt
new file mode 100644
index 0000000000000..dd3b21b412e39
--- /dev/null
+++ b/sound/items/handling/reagent_containers/plastic_bottle/attribution.txt
@@ -0,0 +1,2 @@
+plastic_bottle_liquid_slosh:
+liquid in bottle shaking by mrrap4food -- https://freesound.org/s/470606/ -- License: Creative Commons 0
diff --git a/sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh1.ogg b/sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh1.ogg
new file mode 100644
index 0000000000000..597a7b2fb5f8a
Binary files /dev/null and b/sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh1.ogg differ
diff --git a/sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh2.ogg b/sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh2.ogg
new file mode 100644
index 0000000000000..4f8eb03293f34
Binary files /dev/null and b/sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh2.ogg differ
diff --git a/strings/fishing_tips.txt b/strings/fishing_tips.txt
index 6b25cc5e5391c..f7143c8796665 100644
--- a/strings/fishing_tips.txt
+++ b/strings/fishing_tips.txt
@@ -35,6 +35,7 @@ Some species of fish can be bred into new species under the right conditions.
Most fish don't survive outside water, so get them somewhere safe like an aquarium or a fish case, or even a toilet or a moisture trap!
No matter how you look at it, most people won't care about fishing. Don't let that stop you. They're just jealous.
To fish on ice you have to puncture the ice layer with a pick or shovel first.
+Fishing rods are particularly effective melee weapons against spacemen deeply infused with fish DNA from genetics.
Depending on the kinds of fish inside it and whether they're alive or dead, an aquarium can improve the beauty of the room or worsen it.
Almost all fish can be ground in an All-in-one-Grinder. Don't think too hard about how you're fitting a giant fish into a blender. Nanotrasen technology is weird like that.
The sludgefish from the toilets can be used as a steady supply of cheap fish and fillets due to its self-reproducing behaviour. However it's quite fragile.
@@ -43,7 +44,10 @@ The legendary fishing hat isn't just cosmetic. Space carps (as well as young lob
Have you ever heard a lobster or crab talk? Well, neither have I, but they say they're quite the fishy punsters.
You can get an experiscanner from science to perform fish scanning experiments, which can unlock more modules for the fishing portal, as well as fishing technology nodes (better equipment) to research.
Fish is, of course, edible. Is it safe to eat raw? Well, if you've strong stomach, otherwise your best option is to cook it for a at least half a spessman minute if you don't want to catch nasty diseases.
-After researching the Advanced Fishing Technology Node, you can print special fishing gloves that let you fish without having to carry around a fishing rod. There's one pair that even trains athletics on top of fishing.You can get an experiscanner from science to perform fish scanning experiments, which can unlock more modules for the fishing portal, as well as fishing technology nodes (better equipment) for research.
-If you have enough credits, you can buy a set of fishing lures from cargo. Each lure allows you to catch different species of fish and won't get consumed, however they need to be spun at intervals to work.
+After researching the Advanced Fishing Technology Node, you can print special fishing gloves that let you fish without having to carry around a fishing rod. There's one pair that even trains athletics on top of fishing. You can get an experiscanner from science to perform fish scanning experiments, which can unlock more modules for the fishing portal, as well as fishing technology nodes (better equipment) for research.
+If you have enough credits, you can buy a set of fishing lures from cargo (or the library vending machine). Each lure allows you to catch different species of fish and won't get consumed, however they need to be spun at intervals to work.
Various clothing and handheld items, as well as chairs you sit on, can make fishing easier (or sometimes harder). A trained fisherman can tell what can help and what won't, so keep an eye out.
-This may sound silly, but (live) squids and their ink sacs can be used as weapons to temporarily blind foes.
\ No newline at end of file
+This may sound silly, but (live) squids and their ink sacs can be used as weapons to temporarily blind foes.
+Got a bioelectricity generator aquarium but no electrogenic fish? Worry not, you can always feed your fish some teslium or liquid electricity to temporarily grant them (slightly less powerful) eletrogenesis. It'll also gradually hurt them however.
+Fish can grow in size and weight if you fed them somewhat frequently. Giving them growth serum (from fly amanita) will also boost the rate at which they grow.
+Feeding a fish mutagen can triple the probability of generating evolved offsprings, provided it has an evolution.
\ No newline at end of file
diff --git a/strings/tcg/keywords.json b/strings/tcg/keywords.json
index 854ac936fef1e..869dc8dbe607a 100644
--- a/strings/tcg/keywords.json
+++ b/strings/tcg/keywords.json
@@ -1,18 +1,18 @@
{
- "Asimov": "Creatures possessing this trait cannot attack or defend against creatures with the Human subtype",
+ "Asimov": "Does not deal damage to other creature cards, only to players.",
"Blocker": "The creature cannot declare attacks, but can defend",
- "Changeling": "This creature possesses all creature subtypes simultaneously. Any effects which affect a specific subtype apply to Changelings",
- "Clockwork": "The creature can copy a single keyword on another creature on the field, until they lose the clockwork keyword or leave the field",
- "Deadeye": "This creature can always hit opponents, regardless of effects or immunities",
+ "Changeling": "This creature possesses all creature subtypes simultaneously, even outside of the field. Any effects which affect a specific subtype apply to Changelings",
+ "Clockwork": "On Summon: The creature can copy a single keyword on another creature on the field, until they lose the clockwork keyword or leave the field",
+ "Deadeye": "The following effect triggers when the creature damages the opponent.",
"Faction": "Groupings of cards that can often share effects and traits together",
"First Strike": "This creature has attack priority in combat",
- "Fury": "The creature must attack at every possibility",
- "Graytide": "When this creature enters the battlefield, it gains +1/+1 for the number of creatures on your side of the field with Graytide, until the end of the turn",
+ "Fury": "This creature is not tapped when it kills another unit in combat.",
+ "Graytide": "This creature gains +1/+1 for the number of other creatures on your side of the field with Graytide.",
"Hivemind": "The creature enters combat with a hivemind token on it.The first time this card would take damage, remove that token instead. This does not apply to immediate removal effects, only points of damage",
"Holy": "Immunity to all event cards",
"Immunity": "The creature cannot be affected by card effects or combat of its immunity type. This includes both friendly and opposing effects",
"On Equip": "This effect is activated once its item cost is paid and it enters the battlefield, equipping itself to a chosen card on your side of the field (Unless otherwise stated)",
"On Summon": "This effect is activated once its creature cost is paid and it enters the battlefield",
- "Squad Tactics": "When this creature attacks an opponent's creature and defeats it in combat, the owner of the defeated card takes 1 lifeshard of damage from combat",
- "Taunt": "All opposing creature attacks must be directed towards the creature with Taunt"
+ "Squad Tactics": "This effect is activated when a card is added to your hand.",
+ "Taunt": "The following effect triggers when this card is on the field and any other card is added to your hand."
}
diff --git a/strings/tcg/set_one.json b/strings/tcg/set_one.json
index de24543a66b96..73851198cd267 100644
--- a/strings/tcg/set_one.json
+++ b/strings/tcg/set_one.json
@@ -13,9 +13,9 @@
"id": "AI",
"name": "AI",
"desc": "The latest generation of NT's top secret artificial intelligence project, this time with actual human brains in a jar! Don't tell the press though.",
- "rules": "{$Asimov}",
+ "rules": "{$Asimov} {$Fury}",
"icon_state": "ai",
- "power": "3",
+ "power": "5",
"resolve": "6",
"faction": "Science",
"summoncost": "5",
@@ -29,14 +29,14 @@
"id": "stickman",
"name": "Angry Stickman",
"desc": "Sure, he's flat and crudely drawn, but watch out! He's a menace!",
- "rules": "{$On Summon}: If another 'Angry Stickman' card has been destroyed, you may summon it for at double cost. This ability may be activated only once per turn.",
+ "rules": "{$On Summon}: You can pay 1 plasma to summon an Angry Stickman from the discard pile.",
"icon_state": "angry_stickman",
"power": "1",
"resolve": "1",
"faction": "Xeno",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Construct",
+ "cardsubtype": "Wizardry",
"rarity": "uncommon",
"summon_icon_file": "icons/mob/simple/animal.dmi",
"summon_icon_state": "stickman"
@@ -45,7 +45,7 @@
"id": "changeling",
"name": "Armoured Changeling",
"desc": "The strange creatures known as changelings have been known to develop natural armour as a defense mechanism when in combat.",
- "rules": "{$Changeling}",
+ "rules": "{$Changeling} {$Deadeye}: Add 1 creature with Changeling from your Deck to your hand.",
"icon_state": "armored_changeling",
"power": "2",
"resolve": "8",
@@ -61,14 +61,14 @@
"id": "assistant",
"name": "Staff Assistant",
"desc": "The lowest ladder on the Nanotrasen Employment Ladder, Staff Assistants are employed to help out with tasks deemed 'too menial for robots'.",
- "rules": "{$Graytide}, for every card with '{$Graytide}', this card has +1/+1 on the field.",
+ "rules": "{$Graytide}. The buff this card gains from Graytide is doubled.",
"icon_state": "assistant",
"power": "1",
"resolve": "1",
"faction": "Service",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Maintenance Service",
"rarity": "common",
"summon_icon_state": "Assistant"
},
@@ -76,14 +76,14 @@
"id": "atmos_tech",
"name": "Atmospheric Technician",
"desc": "The Atmospheric Technicians are tasked with keeping the station's air clean, breathable, and, most importantly, devoid of plasma.",
- "rules": "{$On Summon}: Search your deck for an Atmospherics Battlefield card, and add it to your hand. Shuffle your deck afterward.",
+ "rules": "{$On Summon}: Search your deck for a Battlefield card, and add it to your hand. Shuffle your deck afterward.",
"icon_state": "atmos_tech",
- "power": "2",
- "resolve": "3",
+ "power": "3",
+ "resolve": "2",
"faction": "Engineering",
- "summoncost": "4",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Human Engineer",
+ "cardsubtype": "Atmospherics Engineering",
"rarity": "common",
"summon_icon_state": "Atmospheric Technician"
},
@@ -91,14 +91,14 @@
"id": "bartender",
"name": "Bartender",
"desc": "Prior to the introduction of on-station psychologists, the Bartender served to alleviate many employees' woes and fears. Remember, always drink responsibly.",
- "rules": "",
+ "rules": "All your other creatures gain +1 power.",
"icon_state": "bartender",
- "power": "3",
+ "power": "2",
"resolve": "2",
"faction": "Service",
- "summoncost": "3",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Service Bar",
"rarity": "common",
"summon_icon_state": "Bartender"
},
@@ -106,14 +106,14 @@
"id": "botanist",
"name": "Botanist",
"desc": "The Botanist is in charge of keeping the station's food supply happy, healthy, and preferably not laced with hallucinogens.",
- "rules": "",
+ "rules": "Start of turn: Heal 1 lifeshard.",
"icon_state": "botanist",
- "power": "1",
- "resolve": "4",
+ "power": "0",
+ "resolve": "3",
"faction": "Service",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Service Bar Plant",
"rarity": "common",
"summon_icon_state": "Botanist"
},
@@ -121,14 +121,14 @@
"id": "captain",
"name": "Captain",
"desc": "Every Captain is expected to lay down their life for their assigned station. Any Captain who returns to Centcom alive without permission is ceremonially executed before being cloned and stripped of rank.",
- "rules": "Tap this card: inflict -1/-1 to an opposing creature card.",
+ "rules": "Tap: Reduce the power of all opponent creatures to 0 for this turn.",
"icon_state": "captain",
"power": "5",
"resolve": "5",
"faction": "Command",
"summoncost": "7",
"cardtype": "Creature",
- "cardsubtype": "Human Commander",
+ "cardsubtype": "Command",
"rarity": "rare",
"summon_icon_state": "Captain"
},
@@ -138,26 +138,26 @@
"desc": "A heavily customized Apadyne Technologies Mk.2 R.I.O.T. Suit, rebuilt and refitted to Nanotrasen's highest standards for issue to Station Captains.",
"rules": "{$On Equip}: tap the equipped card for 2 turns, without triggering the target card's effects.",
"icon_state": "captain_hardsuit",
- "power": "-1",
- "resolve": "5",
+ "power": "0",
+ "resolve": "4",
"faction": "Command",
- "summoncost": "3",
+ "summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Command",
"rarity": "epic"
},
{
"id": "cargo_tech",
"name": "Cargo Technician",
"desc": "The grunts of Cargo. Any reports that Cargo Technicians are frequently overcome by revolutionary fervour are exaggerated.",
- "rules": "Once per turn, you may give 'Cargo Technician' -1/0 until the start of your next turn and gain 1 mana.",
+ "rules": "Tap: Gain 1 plasma.",
"icon_state": "cargo_tech",
- "power": "3",
- "resolve": "1",
+ "power": "1",
+ "resolve": "2",
"faction": "Cargo",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Cargo Maintenance",
"rarity": "common",
"summon_icon_state": "Cargo Technician"
},
@@ -170,23 +170,23 @@
"power": "2",
"resolve": "2",
"faction": "Service",
- "summoncost": "2",
+ "summoncost": "3",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Service Wizardry",
"rarity": "common"
},
{
"id": "chemist",
"name": "Chemist",
"desc": "Chemists are encouraged to not set up illicit methamphetamine factories on the company's dime.",
- "rules": "Tap this card: flip a coin. If heads: a friendly Medical {$Faction} card gains 0/+2. If tails, an opponents creature of your choice gains +2/0.",
+ "rules": "Tap: Give another unit +2/+2. Destroy it at the end of the turn.",
"icon_state": "chemist",
"power": "0",
"resolve": "3",
"faction": "Medical",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Human Doctor",
+ "cardsubtype": "Medical",
"rarity": "common",
"summon_icon_state": "Chemist"
},
@@ -194,14 +194,14 @@
"id": "CE",
"name": "Chief Engineer",
"desc": "The Chief Engineer is in charge of keeping the station powered and intact.",
- "rules": "If a battlefield card would otherwise be destroyed by an opponent's card effect, you may sacrifice an Engineering faction card of yours in play to negate the battlefield's destruction.",
+ "rules": "Battlefield cards you control cannot be destroyed.",
"icon_state": "ce",
- "power": "3",
- "resolve": "6",
+ "power": "2",
+ "resolve": "2",
"faction": "Engineering",
- "summoncost": "5",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Engineer",
+ "cardsubtype": "Command Engineer",
"rarity": "uncommon",
"summon_icon_state": "Chief Engineer"
},
@@ -209,14 +209,14 @@
"id": "ce_suit",
"name": "Nakamura Engineering R.I.G.Suit (Advanced)",
"desc": "An updated version of Nakamura Engineering's R.I.G.Suit, fitted with advanced radiation shielding and extra armour.",
- "rules": "Tap this card: tap the equipped creature. The equipped creature avoids the effects of the active battlefield until removed from the field.",
+ "rules": "On Equip: Tap the equipped creature. It gains Immunity from Battlefields.",
"icon_state": "ce_hardsuit",
- "power": "0",
- "resolve": "3",
+ "power": "1",
+ "resolve": "2",
"faction": "Engineering",
- "summoncost": "3",
+ "summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Engineering Syndicate Cybersun",
"rarity": "rare"
},
{
@@ -230,7 +230,7 @@
"faction": "Medical",
"summoncost": "5",
"cardtype": "Creature",
- "cardsubtype": "Human Doctor",
+ "cardsubtype": "Medical Command",
"rarity": "common",
"summon_icon_state": "Chief Medical Officer"
},
@@ -238,28 +238,28 @@
"id": "cmo_suit",
"name": "DeForest Medical Corporation 'Lifesaver' Carapace",
"desc": "An advanced voidsuit designed for emergency medical personnel. Features include a built-in medical HUD and advanced medical gauntlets.",
- "rules": "Tap this card: tap the equipped creature and re-equip 'DeForest Medical Corporation 'Lifesaver' Carapace' on a different creature on your side of the field. This effect may be activated once per turn.",
+ "rules": "Once per turn: equip 'DeForest Medical Corporation 'Lifesaver' Carapace' on a different creature on your side of the field.",
"icon_state": "cmo_hardsuit",
"power": "1",
"resolve": "3",
"faction": "Medical",
- "summoncost": "3",
+ "summoncost": "2",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Medical",
"rarity": "uncommon"
},
{
"id": "chrono",
"name": "Chrono Legionnaire",
"desc": "Currently in the earliest stages of development, the Chrono Legionnaire project is expected to weaponize time itself.",
- "rules": "If this card is destroyed or discarded, flip 3 coins. If the result has 2 or more heads, add this card back to your hand. Otherwise, send it to your graveyard.",
+ "rules": "If this card is destroyed flip a coin. If the result is heads, add this card back to your hand.",
"icon_state": "chrono_legionnaire",
- "power": "6",
- "resolve": "2",
+ "power": "3",
+ "resolve": "3",
"faction": "Security",
- "summoncost": "4",
+ "summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Soldier",
+ "cardsubtype": "Wizardry Maintenance",
"rarity": "epic",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "chrono"
@@ -268,14 +268,14 @@
"id": "sloth",
"name": "Citrus",
"desc": "Cargo's happy sloth pal. Known for his cute sweater and always getting in the way.",
- "rules": "Tap this card: Tap an opponent's card until the start of your next turn",
+ "rules": "Tap: Tap an opponent's card until the start of your next turn",
"icon_state": "citrus",
"power": "0",
"resolve": "3",
"faction": "Cargo",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Sloth",
+ "cardsubtype": "Cargo Animal",
"rarity": "common",
"summon_icon_file": "icons/mob/simple/pets.dmi",
"summon_icon_state": "cool_sloth"
@@ -286,12 +286,12 @@
"desc": "Every Nanotrasen station has a clown on board, as high command believes that a source of entertainment will reduce instances of murder-suicide on board Spinward Stations. The results of this hypothesis are, as of yet, unproven.",
"rules": "{$Taunt}",
"icon_state": "clown",
- "power": "2",
- "resolve": "4",
+ "power": "1",
+ "resolve": "3",
"faction": "Service",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Clown",
+ "cardsubtype": "Bar Maintenance Abomination Clown",
"rarity": "common",
"summon_icon_state": "Clown"
},
@@ -306,7 +306,7 @@
"faction": "Service",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon Clown",
+ "cardsubtype": "Silicon Bar Clown",
"rarity": "uncommon",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "clown"
@@ -321,7 +321,7 @@
"resolve": "0",
"faction": "Service",
"summoncost": "1",
- "cardtype": "Equipment",
+ "cardtype": "Clown",
"cardsubtype": "Armour",
"rarity": "epic"
},
@@ -329,7 +329,7 @@
"id": "abductor_armour",
"name": "Abductor Combat Armour",
"desc": "Recovered from the strange alien species known as the Abductors, this armour is made from an extremely tough yet flexible material that has been dubbed as Alien Alloy by researchers.",
- "rules": "{$On Equip}: give the equipped unit Effect {$Immunity} and Spell {$Immunity}.",
+ "rules": "{$On Equip}: give the equipped unit Holy.",
"icon_state": "abductor_combat",
"power": "1",
"resolve": "3",
@@ -350,7 +350,7 @@
"faction": "Service",
"summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Bar Plants",
"rarity": "common",
"summon_icon_state": "Cook"
},
@@ -365,21 +365,21 @@
"faction": "Syndicate",
"summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Wizardry",
"rarity": "rare"
},
{
"id": "curator",
"name": "Curator",
"desc": "In Nanotrasen polls, the Curator has ranked as the most pointless job on station, much to the ire of the Curator's union. Thankfully, we don't have to listen to them.",
- "rules": "{$On Summon}: Draw 1 card: if it's an event card, discard it.",
+ "rules": "{$On Summon}: Draw 1 card.",
"icon_state": "curator",
"power": "1",
"resolve": "1",
"faction": "Service",
- "summoncost": "2",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Worker",
+ "cardsubtype": "Wizardry",
"rarity": "common",
"summon_icon_state": "Curator"
},
@@ -394,7 +394,7 @@
"faction": "Science",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon Corgi",
+ "cardsubtype": "Silicon Animal",
"rarity": "rare",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "borgi"
@@ -405,26 +405,26 @@
"desc": "The most advanced set of armour available for purchase from Apadyne Technologies, the Mk.3 R.I.O.T. Carapace is issued to Nanotrasen's most elite forces.",
"rules": "{$On Equip}: if the equipped creature is of the Security faction, it gains {$Taunt}.",
"icon_state": "deathsquad",
- "power": "3",
- "resolve": "3",
+ "power": "2",
+ "resolve": "1",
"faction": "Security",
- "summoncost": "1",
+ "summoncost": "2",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Security ERT",
"rarity": "epic"
},
{
"id": "det",
"name": "Detective",
"desc": "Nanotrasen hires nothing but the best detectives to investigate crime on our stations. A penchant for cigarettes and outdated fashion isn't mandatory, but is appreciated.",
- "rules": "{$Deadeye}",
+ "rules": "{$Deadeye}: Draw 1 card. Cannot be attacked if you have other untapped creatures.",
"icon_state": "detective",
"power": "3",
"resolve": "2",
"faction": "Security",
- "summoncost": "5",
+ "summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Officer",
+ "cardsubtype": "Maintenance Bar",
"rarity": "uncommon",
"summon_icon_state": "Detective"
},
@@ -437,9 +437,9 @@
"power": "5",
"resolve": "5",
"faction": "Syndicate",
- "summoncost": "7",
+ "summoncost": "8",
"cardtype": "Creature",
- "cardsubtype": "Syndicate Soldier",
+ "cardsubtype": "Syndicate Cybersun",
"rarity": "rare",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "syndicate_stormtrooper_sword"
@@ -448,14 +448,14 @@
"id": "engiborg",
"name": "Cyborg (Engineering Shell)",
"desc": "A common sight on Nanotrasen Stations, Engineering Shells maintain critical station systems in hazardous conditions.",
- "rules": "{$Asimov}",
+ "rules": "{$Asimov} Tap: Draw 1 card.",
"icon_state": "borg_engi",
"power": "2",
"resolve": "2",
"faction": "Engineering",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon",
+ "cardsubtype": "Silicon Engineering Atmosian",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "engineer"
@@ -464,41 +464,41 @@
"id": "ert_command",
"name": "NT P.A.V. Suit (Command)",
"desc": "Issued to members of Emergency Response Teams, the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with a sidearm holster and a sleek blue finish.",
- "rules": "While equipped, give the equipped unit {$Squad Tactics} and {$First Strike}.",
+ "rules": "While equipped, give the equipped unit {$First Strike}.",
"icon_state": "ert_command",
- "power": "2",
- "resolve": "2",
+ "power": "0",
+ "resolve": "0",
"faction": "Command",
"summoncost": "2",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Command ERT",
"rarity": "rare"
},
{
"id": "ert_engi",
"name": "NT P.A.V. Suit (Engineering)",
"desc": "Issued to members of Emergency Response Teams, the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with a welding screen and a flashy yellow finish.",
- "rules": "While equipped, give the equipped unit {$Squad Tactics}.",
+ "rules": "While equipped, give the equipped unit {$Squad Tactics}: Gain +1/+1 until the end of the turn.",
"icon_state": "ert_engi",
"power": "1",
"resolve": "1",
"faction": "Engineering",
"summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "ERT Atmospherics",
"rarity": "uncommon"
},
{
"id": "ert_med",
"name": "NT P.A.V. Suit (Medical)",
"desc": "Issued to members of Emergency Response Teams, the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with a sterile coating and a calming white finish.",
- "rules": "While equipped, give the equipped unit {$Squad Tactics}.",
+ "rules": "Whenever the equipped unit deals damage it heals you (the player) for that amount.",
"icon_state": "ert_med",
"power": "1",
- "resolve": "2",
+ "resolve": "1",
"faction": "Medical",
"summoncost": "2",
- "cardtype": "Equipment",
+ "cardtype": "ERT Medical",
"cardsubtype": "Armour",
"rarity": "uncommon"
},
@@ -506,28 +506,28 @@
"id": "ert_sec",
"name": "NT P.A.V. Suit (Security)",
"desc": "Issued to members of Emergency Response Teams, the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with bulletproof padding and an intimidating red finish.",
- "rules": "While equipped, give the equipped unit {$Squad Tactics}.",
+ "rules": "While equipped, give the equipped unit {$Fury}.",
"icon_state": "ert_sec",
- "power": "2",
- "resolve": "1",
+ "power": "-1",
+ "resolve": "0",
"faction": "Security",
"summoncost": "2",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "ERT Security",
"rarity": "uncommon"
},
{
"id": "explorer",
"name": "Explorer",
"desc": "The Nanotrasen Explorers Corps boldly goes where humanity has never gone before. Or would, if they weren't buried under mounds of bureaucracy.",
- "rules": "You may tap this card: Flip a coin, if heads, gain 4 mana this turn, if tails, tap this card for 2 turns.",
+ "rules": "Tap: Flip a coin, if heads, gain 1 plasma.",
"icon_state": "explorer",
"power": "2",
"resolve": "2",
"faction": "Cargo",
- "summoncost": "2",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Explorer",
+ "cardsubtype": "Atmospherics",
"rarity": "legendary",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "explorer"
@@ -541,7 +541,7 @@
"power": "3",
"resolve": "3",
"faction": "Science",
- "summoncost": "2",
+ "summoncost": "1",
"cardtype": "Creature",
"cardsubtype": "Silicon",
"rarity": "common",
@@ -552,14 +552,14 @@
"id": "geneticist",
"name": "Geneticist",
"desc": "Geneticists are tasked with manipulating human DNA to produce special effects. Nanotrasen maintains a strict 'no superhero' policy for mutations, following the Superhero Civil War of 2150.",
- "rules": "You may tap this card and pay 3 mana: Give a friendly creature {$Hivemind} until this card leaves the field.",
+ "rules": "Tap: Give a friendly creature {$Hivemind} until this card leaves the field.",
"icon_state": "geneticist",
- "power": "3",
- "resolve": "4",
+ "power": "2",
+ "resolve": "3",
"faction": "Science",
"summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Scientist",
+ "cardsubtype": "Science Abomination",
"rarity": "common",
"summon_icon_state": "Geneticist"
},
@@ -567,14 +567,14 @@
"id": "med_geneticist",
"name": "Geneticist",
"desc": "Geneticists are tasked with manipulating human DNA to produce special effects. Nanotrasen maintains a strict 'no superhero' policy for mutations, following the Superhero Civil War of 2150.",
- "rules": "{$Graytide}, {$Hivemind}",
+ "rules": "All friendly creatures everywhere gain {$Hivemind} while this card is on the field.",
"icon_state": "geneticist_med",
- "power": "3",
- "resolve": "6",
+ "power": "2",
+ "resolve": "3",
"faction": "Medical",
- "summoncost": "8",
+ "summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Doctor",
+ "cardsubtype": "Science Abomination",
"rarity": "misprint",
"summon_icon_state": "Geneticist"
},
@@ -582,14 +582,14 @@
"id": "spookian",
"name": "Ghost Ian",
"desc": "Oh my god! Ian's dead!",
- "rules": "{$On Summon}: Search your deck for a battlefield, and add it to your hand. Shuffle your deck afterwards.",
+ "rules": "When destroyed: Add this card back to your hand.",
"icon_state": "ian_ghost",
"power": "1",
"resolve": "1",
"faction": "Service",
- "summoncost": "3",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Spirit Corgi",
+ "cardsubtype": "Wizardry Animal",
"rarity": "epic",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "ghost"
@@ -598,14 +598,14 @@
"id": "HOP",
"name": "Head of Personnel",
"desc": "The head of the Cargo and Service Departments, guardian of all access, and Ian's lovable, yet dumb, sidekick.",
- "rules": "Once per turn: Select a friendly creature card. That card gains {$Changeling}.",
+ "rules": "Tap: Another animal you control gains Holy and cannot be damaged for the rest of this turn.",
"icon_state": "hop",
- "power": "4",
- "resolve": "3",
+ "power": "2",
+ "resolve": "2",
"faction": "Service",
- "summoncost": "7",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Commander",
+ "cardsubtype": "Command Animal",
"rarity": "uncommon",
"summon_icon_state": "Head of Personnel"
},
@@ -613,14 +613,14 @@
"id": "HOS",
"name": "Head of Security",
"desc": "Nanotrasen hires most heads of staff based on their qualifications as being amicable, good at conflict resolution, ability to handle high-stakes situations, humanity, and desire to learn. Heads of Security only need a highschool degree.",
- "rules": "{$On Summon}: Select a card type. That card type now costs 1 extra mana to summon. This effect persists until Head of Security is removed from the battlefield.",
+ "rules": "Must declare a direct attack when attacking (but can still be blocked). {$Deadeye}: Deal 3 damage to your opponent or a unit. {$Fury} ",
"icon_state": "hos",
"power": "4",
- "resolve": "4",
+ "resolve": "6",
"faction": "Security",
"summoncost": "7",
"cardtype": "Creature",
- "cardsubtype": "Human Officer",
+ "cardsubtype": "Command Security",
"rarity": "uncommon",
"summon_icon_state": "Head of Security"
},
@@ -628,28 +628,28 @@
"id": "hos_suit",
"name": "Apadyne Technologies 'Tyrant' Class Hardshell",
"desc": "The distinctive shape of the Tyrant Class Hardshell is caused, in part, by the large amount of kevlar reinforcement and the ablative armour layer. Perhaps more importantly, it also looks rad.",
- "rules": "Grant the equip card {$Fury} until this card is removed from play.",
+ "rules": "When the equipped card destroys a creature: It deals 1 damage to another creature.",
"icon_state": "hos_hardsuit",
- "power": "4",
- "resolve": "2",
+ "power": "1",
+ "resolve": "1",
"faction": "Security",
- "summoncost": "5",
+ "summoncost": "3",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Command Security",
"rarity": "rare"
},
{
"id": "ian",
"name": "Ian",
"desc": "This adorable corgi has become the defacto mascot of the Spinward Stations to many. He comes in many forms, many sizes, and many shapes, but he's still just as lovable. Hand wash only.",
- "rules": "{$Holy}, You may Sacrifice this card on the field: Play a Command card from your hand for free.",
+ "rules": "{$Holy} On destroyed: Both players can summon a creature from their hand for free.",
"icon_state": "ian",
"power": "0",
"resolve": "3",
"faction": "Service",
- "summoncost": "4",
+ "summoncost": "5",
"cardtype": "Creature",
- "cardsubtype": "Corgi",
+ "cardsubtype": "Animal",
"rarity": "common",
"summon_icon_file": "icons/mob/simple/pets.dmi",
"summon_icon_state": "corgi"
@@ -658,28 +658,28 @@
"id": "inquisitor_suit",
"name": "Inquisitor's Hardsuit",
"desc": "Nanotrasen officially doesn't believe in ghosts, magic, or anything that can't be solved with science. When you see someone show up in one of these, let that remind you of that fact.",
- "rules": "Apply {$First Strike} to the equip creature.",
+ "rules": "Shuffle into the opponent's deck any creatures destroyed by battle with the equipped creature. The shuffled card's on destruction effects do not activate.",
"icon_state": "inquisitor",
- "power": "2",
- "resolve": "2",
+ "power": "1",
+ "resolve": "1",
"faction": "Service",
- "summoncost": "4",
+ "summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Wizardry ERT",
"rarity": "epic"
},
{
"id": "intern",
"name": "Intern",
"desc": "All Nanotrasen interns come with 3 things: A resume, a desire to learn, and vague promises that they're getting paid at some point. So don't be too rough on them.",
- "rules": "{$First Strike}",
+ "rules": "{$First Strike} {$Graytide}",
"icon_state": "intern",
"power": "1",
"resolve": "1",
"faction": "Command",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "ERT Maintenance",
"rarity": "common",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "intern"
@@ -688,14 +688,14 @@
"id": "jannie",
"name": "Janitor",
"desc": "A true testament to futility, they clean and they clean and they clean, knowing that there's no way they can clean it all. Yet, they persevere, knowing that without them, the crew would simply give in to their base animalistic nature.",
- "rules": "{$Taunt}",
+ "rules": "{$Taunt} {$Deadeye}: Shuffle a card from the opponent's discard pile to their deck.",
"icon_state": "janitor",
"power": "1",
- "resolve": "1",
+ "resolve": "2",
"faction": "Service",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Maintenance",
"rarity": "common",
"summon_icon_state": "Janitor"
},
@@ -703,14 +703,14 @@
"id": "jannieborg",
"name": "Cyborg (Custodial Shell)",
"desc": "A powerful, state of the act cleaning machine. They exist to eradicate stains, snag garbage, and replace lights, forever. We are legally obligated by the Janitor's Union to state that these machines are no replacement for a flesh-and-blood janitor.",
- "rules": "{$Asimov}, you may tap this card: Tap an opponent's Human Creature as well.",
+ "rules": "{$Asimov} {$Deadeye}: Shuffle up to 3 cards from the opponent's discard pile to their deck.",
"icon_state": "borg_janitor",
"power": "1",
"resolve": "3",
"faction": "Service",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon",
+ "cardsubtype": "Silicon Maintenance",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "janitor"
@@ -719,14 +719,14 @@
"id": "lawyer",
"name": "Lawyer",
"desc": "Nanotrasen knows the value of a good lawyer. That's why they're all working hard at our home offices defending us from frivolous labor suits from lazy no-good employees who should be working hard instead of slacking off reading trading cards.",
- "rules": "When an opponent attacks with a creature with 3 or more power, this card gains {$Taunt}.",
+ "rules": "When an opponent attacks with a creature with 3 or less power, this card gains {$Taunt}.",
"icon_state": "lawyer",
"power": "0",
"resolve": "4",
"faction": "Service",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Service Security",
"rarity": "common",
"summon_icon_state": "Lawyer"
},
@@ -734,7 +734,7 @@
"id": "legion",
"name": "Legion",
"desc": "They are the cursed, damned souls of civilizations born and lost in the flames of Indecipheres, conglomerated into a lump of emaciated bodies, wandering the realms they used to rule... or something along those lines, anyway.",
- "rules": "When Legion is destroyed, search your deck for a human card, and summon it to the battlefield. Shuffle your deck afterward.",
+ "rules": "When Legion is destroyed, search your deck for a creature card costing 3 or less and add it to your hand. Shuffle your deck afterward.",
"icon_state": "legion",
"power": "2",
"resolve": "1",
@@ -750,14 +750,14 @@
"id": "medborg",
"name": "Cyborg (Medical Shell)",
"desc": "A state of the art medical shell, for when biological life just can't take care of itself. Comes equipped with built-in surgical equipment and all the medicated lollipops you could ever want.",
- "rules": "{$Asimov}, you may tap this card and pay 2 mana: Reset a card's resolve to its original value.",
+ "rules": "{$Asimov} Tap: Reset a card's resolve to its original value (if possible) and heal it (if possible).",
"icon_state": "borg_medical",
"power": "2",
"resolve": "3",
"faction": "Medical",
- "summoncost": "4",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon Doctor",
+ "cardsubtype": "Silicon Medical",
"rarity": "uncommon",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "medical"
@@ -766,14 +766,14 @@
"id": "doc",
"name": "Medical Doctor",
"desc": "Nanotrasen's doctors are well known for their ability to treat almost any ailment known to mankind... as well as causing a fair few in the process.",
- "rules": "You may tap this card: Select a card that has less attack than this card from your graveyard, and summon it to your side of the field.",
+ "rules": "Tap: Add a card from your discard pile to your hand, except another 'Medical Doctor'.",
"icon_state": "md",
- "power": "2",
+ "power": "1",
"resolve": "3",
"faction": "Medical",
"summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Doctor",
+ "cardsubtype": "Medical",
"rarity": "common",
"summon_icon_state": "Medical Doctor"
},
@@ -781,14 +781,14 @@
"id": "mime",
"name": "Mime",
"desc": "Si vous regardez attentivement dans les yeux d'un mime, vous pouvez voir le tourment sans fin derrière leur façade silencieuse. C'est vraiment tragique.",
- "rules": "You may tap this card: Pick an opponent's card and nullify its effect until it leaves play.",
+ "rules": "Tap: Target creature and all its attached equipment loses all its effect text until this card leaves the field.",
"icon_state": "mime",
- "power": "2",
+ "power": "0",
"resolve": "1",
"faction": "Service",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Mime",
+ "cardsubtype": "Bar",
"rarity": "uncommon",
"summon_icon_state": "Mime"
},
@@ -796,14 +796,14 @@
"id": "miningborg",
"name": "Cyborg (Mining Shell)",
"desc": "Fitted with a drill and tracks, the Mining Shell is designed to hold up to the rigors of mining, be that on the hellish surface of Indecipheres, or in the silent vacuum of the asteroid belt.",
- "rules": "{$Asimov}, at the end of your turn, if this card is not tapped, you may tap this card at the start of your next turn to gain 1 mana.",
+ "rules": "{$Asimov} At the start of your turn: Tap: Gain 2 plasma.",
"icon_state": "borg_miner",
"power": "3",
- "resolve": "1",
+ "resolve": "2",
"faction": "Cargo",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon Miner",
+ "cardsubtype": "Silicon Cargo",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "minerOLD"
@@ -812,14 +812,14 @@
"id": "monkey",
"name": "Monkey",
"desc": "Nanotrasen seeks to phase out animal testing by 2570, in accordance with new TerraGov legislation. This will be replaced with more ethical solutions, such as computer simulations, or experimentation on Staff Assistants.",
- "rules": "{$Graytide}, this card is considered Human with a Geneticist on your side of the field.",
+ "rules": "{$Graytide} {$Squad Tactics}: Deal 1 damage to your opponent.",
"icon_state": "monkey",
"power": "1",
"resolve": "1",
"faction": "Science",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Primate",
+ "cardsubtype": "Animal Science",
"rarity": "common",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "monkey"
@@ -828,14 +828,14 @@
"id": "nukeop",
"name": "Nuclear Operative",
"desc": "The frontline grunts of the syndicate army, Nuclear Operatives are typically well trained and equipped for their grim duty.",
- "rules": "{$Squad Tactics}",
+ "rules": "On Play: Gain +1/+1 until the end of the turn",
"icon_state": "nukie_red",
- "power": "4",
- "resolve": "2",
+ "power": "3",
+ "resolve": "3",
"faction": "Syndicate",
- "summoncost": "4",
+ "summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Syndicate Soldier",
+ "cardsubtype": "Syndicate Cybersun",
"rarity": "rare",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "syndicate_space_shotgun"
@@ -851,7 +851,7 @@
"faction": "Medical",
"summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Doctor",
+ "cardsubtype": "Medical",
"rarity": "common",
"summon_icon_state": "Paramedic"
},
@@ -859,14 +859,14 @@
"id": "peaceborg",
"name": "Cyborg (Peacekeeper Shell)",
"desc": "After the unilateral phasing out of Security Shells in 2554 following mass reports of cyborg-on-human violence, the Peacekeeper Shell was introduced as a stopgap solution until the problems could be resolved.",
- "rules": "{$Asimov}, this card loses -1 power for every creature on your opponent's side of the field",
+ "rules": "{$Asimov} {$Taunt} {$Blocker} When damaged: Return this card to the hand.",
"icon_state": "borg_peace",
"power": "3",
"resolve": "3",
"faction": "Security",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon Officer",
+ "cardsubtype": "Silicon Security",
"rarity": "uncommon",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "peace"
@@ -875,14 +875,14 @@
"id": "plasma_engi",
"name": "Station Engineer (Plasmaman)",
"desc": "The ever industrious plasmamen are well suited to engineering work, due to their natural radiation resistance.",
- "rules": "{$Immunity} to Battlefields",
+ "rules": "{$Immunity} to Battlefields. When this creature dies deal 1 damage to all other creatures.",
"icon_state": "engi_plasma",
"power": "2",
"resolve": "4",
"faction": "Engineering",
- "summoncost": "5",
+ "summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Plasmaman Engineer",
+ "cardsubtype": "Atmospherics",
"rarity": "common",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "plasmaman"
@@ -891,14 +891,14 @@
"id": "QM",
"name": "Quartermaster",
"desc": "Every Nanotrasen station has a Quartermaster, who controls the flow of cargo to and from the station, and by extension to and from the hands of the crew. He's not given the distinction of being a head, though. His job isn't hard enough.",
- "rules": "Pay 3 mana and tap this card: All card cards on your side of the field gain +1/+1 until the end of this turn.",
+ "rules": "You cannot be damaged while this unit is on the field. Holy.",
"icon_state": "qm",
- "power": "3",
- "resolve": "3",
+ "power": "0",
+ "resolve": "7",
"faction": "Cargo",
"summoncost": "6",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Cargo Command",
"rarity": "uncommon",
"summon_icon_state": "Quartermaster"
},
@@ -906,14 +906,14 @@
"id": "qm_head",
"name": "Quartermaster",
"desc": "Every Nanotrasen station has a Quartermaster, who controls the flow of cargo to and from the station, and by extension to and from the hands of the crew.",
- "rules": "Pay 8 mana and permanently tap this card: All cargo cards on your side of the field gain +2/+2 until this card leaves play.",
+ "rules": "You cannot be damaged while this unit is on the field. Holy.",
"icon_state": "qm_head",
- "power": "6",
- "resolve": "6",
+ "power": "7",
+ "resolve": "7",
"faction": "Cargo",
- "summoncost": "10",
+ "summoncost": "6",
"cardtype": "Creature",
- "cardsubtype": "Human Employee",
+ "cardsubtype": "Cargo Command",
"rarity": "misprint",
"summon_icon_state": "Quartermaster"
},
@@ -921,14 +921,14 @@
"id": "rabbit_pai",
"name": "Personal AI Device (Rabbit Shell)",
"desc": "Personal AI Devices are able to take the form of many household pets, to provide a homely sense of comfort and companionship to their owners.",
- "rules": "This card may steal the {$Asimov} keyword off of another friendly silicon creature.",
+ "rules": "Tap: Remove the {$Asimov} keyword off of another friendly silicon creature.",
"icon_state": "pai_rabbit",
"power": "0",
"resolve": "1",
"faction": "Science",
- "summoncost": "2",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Silicon Rabbit",
+ "cardsubtype": "Silicon Creature",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/pai.dmi",
"summon_icon_state": "rabbit"
@@ -937,14 +937,14 @@
"id": "drone_pai",
"name": "Personal AI Device (Drone Shell)",
"desc": "The most basic Personal AI shell, the Drone Shell resembles the old maintainance drones used on Nanotrasen Stations prior to 'the incident', and is perfect for the tech-savvy AI-owner.",
- "rules": "You may pay 1 mana and tap this card: a silicon card may attack one additional time this turn.",
+ "rules": "Tap this card: Add a silicon card from your discard pile to your hand.",
"icon_state": "pai_drone",
"power": "2",
"resolve": "4",
"faction": "Science",
- "summoncost": "5",
+ "summoncost": "4",
"cardtype": "Creature",
- "cardsubtype": "Silicon Drone",
+ "cardsubtype": "Silicon",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/pai.dmi",
"summon_icon_state": "repairbot"
@@ -953,14 +953,14 @@
"id": "RD",
"name": "Research Director",
"desc": "The Research Director is the head of the Science Division, and is responsible for, shockingly, directing research.",
- "rules": "Once per turn, you may tap all Science faction cards in play, activate the effect of an event card twice.",
+ "rules": "Tap: The effect of the next event or instant card you activate this turn is played twice.",
"icon_state": "rd",
"power": "2",
"resolve": "5",
"faction": "Science",
- "summoncost": "7",
+ "summoncost": "6",
"cardtype": "Creature",
- "cardsubtype": "Human Scientist",
+ "cardsubtype": "Science Command",
"rarity": "uncommon",
"summon_icon_state": "Research Director"
},
@@ -968,28 +968,28 @@
"id": "rd_suit",
"name": "Nakamura Engineering B.O.M.B.Suit",
"desc": "The Nakamura Engineering B.O.M.B.Suit is an innovative combination of a R.I.G.Suit and a bomb suit, perfect for toxins research.",
- "rules": "Reduces all battlefield damage to the equipped creature by 2.",
+ "rules": "Damage to the equipped creature cannot exceed 2.",
"icon_state": "rd_hardsuit",
"power": "0",
"resolve": "0",
"faction": "Science",
"summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Science",
"rarity": "rare"
},
{
"id": "roboticist",
"name": "Roboticist",
"desc": "The roboticist's work is as close as Nanotrasen legally allows its employees to come to necromancy.",
- "rules": "If a {$Asimov} card on your side of the field is destroyed, you may pay 2 mana and tap this card: Return that card to your hand.",
+ "rules": "On Play: Add a Silicon from your deck to your hand.",
"icon_state": "roboticist",
"power": "2",
"resolve": "2",
"faction": "Science",
"summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Scientist",
+ "cardsubtype": "Science Silicon",
"rarity": "uncommon",
"summon_icon_state": "Roboticist"
},
@@ -997,14 +997,14 @@
"id": "runtime",
"name": "Runtime",
"desc": "Runtime is the CMO's personal feline companion, and is well known for her laziness. It's said that opening a tin of tuna anywhere on the station will bring her running.",
- "rules": "You may sacrifice this card: reduce the cost of summoning a medical faction card this turn by 2 mana.",
+ "rules": "Sacrifice this card: reduce the cost of summoning your next medical faction card this turn by 2 mana.",
"icon_state": "runtime",
"power": "0",
"resolve": "1",
"faction": "Medical",
- "summoncost": "3",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Cat",
+ "cardsubtype": "Animal Medical",
"rarity": "uncommon",
"summon_icon_file": "icons/mob/simple/pets.dmi",
"summon_icon_state": "cat"
@@ -1013,14 +1013,14 @@
"id": "scientist",
"name": "Scientist",
"desc": "Rumours that Nanotrasen hires 'mad scientists' are greatly exaggerated. Scientists are regularly screened to ensure that their insanity remains within acceptable limits.",
- "rules": "When this card is targeted by an opponent's single target event, you gain 1 lifeshard.",
+ "rules": "{$On Summon}: Destroy a creature with 4 or more power.",
"icon_state": "scientist",
"power": "1",
- "resolve": "2",
+ "resolve": "3",
"faction": "Science",
- "summoncost": "4",
+ "summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Scientist",
+ "cardsubtype": "Science",
"rarity": "common",
"summon_icon_state": "Scientist"
},
@@ -1028,14 +1028,14 @@
"id": "secborg",
"name": "Cyborg (Security Shell)",
"desc": "Following an incident in 2554, the Security Cyborg Shell was unilaterally phased out and replaced by the Peacekeeper. Nonetheless, many units remain in service with various other organisations such as private militaries.",
- "rules": "{$Asimov}, when this card targets a human creature, deal 1 damage to it after the battle resolves.",
+ "rules": "{$Asimov} When this attacks deal 1 damage to the blocker before the battle resolves (ignoring Asimov).",
"icon_state": "borg_sec",
"power": "4",
"resolve": "2",
"faction": "Security",
- "summoncost": "6",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon Officer",
+ "cardsubtype": "Silicon Security",
"rarity": "epic",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "sec"
@@ -1044,14 +1044,14 @@
"id": "sec_officer",
"name": "Security Officer",
"desc": "Nanotrasen would like to remind all employees to support their station security team; remember, the boys in red keep you safe!",
- "rules": "{$Squad Tactics}",
+ "rules": "{$Squad Tactics}: Heal this unit to full resolve.",
"icon_state": "sec",
- "power": "2",
- "resolve": "2",
+ "power": "3",
+ "resolve": "3",
"faction": "Security",
"summoncost": "3",
"cardtype": "Creature",
- "cardsubtype": "Human Officer",
+ "cardsubtype": "Security",
"rarity": "common",
"summon_icon_state": "Security Officer"
},
@@ -1061,26 +1061,26 @@
"desc": "Fashioned from paranormally reinforced brass, the Ratvar Cult's clockwork armour is as beautiful as it is heretical.",
"rules": "While equipped, give the equipped unit {$Clockwork}.",
"icon_state": "clock_cultist",
- "power": "2",
- "resolve": "2",
+ "power": "0",
+ "resolve": "0",
"faction": "Syndicate",
- "summoncost": "4",
+ "summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Armour",
+ "cardsubtype": "Wizardry",
"rarity": "epic"
},
{
"id": "beercanborg",
- "name": "Cyborg (Service Shell- Beercan)",
+ "name": "Cyborg (Service Shell - Beercan)",
"desc": "Despite being based on the Medical Shell, this particular Service Shell is tasked with destroying livers, rather than healing them.",
- "rules": "{$Asimov}, you may discard this card: draw one Service {$Faction} card from your deck, then shuffle.",
+ "rules": "{$Asimov} All enemy creatures gain {$Fury} and -1 Resolve while this is on the field.",
"icon_state": "borg_serv_can",
"power": "1",
"resolve": "1",
"faction": "Service",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Silicon",
+ "cardsubtype": "Silicon Bar",
"rarity": "uncommon",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "kent"
@@ -1089,46 +1089,46 @@
"id": "flamboyantborg",
"name": "Cyborg (Service Shell- Flamboyant)",
"desc": "Sometimes a cyborg just needs to show a bit of flamboyance, you know?",
- "rules": "{$Asimov}, gains +2/+2 when it's the only card on your side of the field.",
+ "rules": "{$Asimov}, gains +2/+2 and loses {$Asimov} when it's the only card on your side of the field.",
"icon_state": "borg_serv_pink",
"power": "0",
"resolve": "1",
"faction": "Service",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Silicon",
+ "cardsubtype": "Silicon Bar",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "brobot"
},
{
"id": "skirtborg",
- "name": "Cyborg (Service Shell- Skirted)",
+ "name": "Cyborg (Service Shell - Skirted)",
"desc": "The Service Shell is intended to be the most human of the Cyborg Shells, due to its outwardly social role- none exemplify this better than the Skirted Shell, showing that even robots can't escape fashion norms.",
- "rules": "{$Asimov}",
+ "rules": "{$Asimov} On Play: You (the player) heal 1 for every creature on your side of the field.",
"icon_state": "borg_serv_skirt",
"power": "0",
"resolve": "3",
"faction": "Service",
- "summoncost": "1",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon",
+ "cardsubtype": "Silicon Bar",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "service_f"
},
{
"id": "classicborg",
- "name": "Cyborg (Service Shell- Classic)",
+ "name": "Cyborg (Service Shell - Classic)",
"desc": "The classic Service Shell, the Classic Shell is what most crewmembers think of when they think of a 'useless robot that serves drinks'.",
- "rules": "{$Asimov}, for every piece of equipment in play, gain +1 temporary resolve during the opponent's turn. That temporary resolve is lost at the start of your turn.",
+ "rules": "{$Asimov}, for every equipment in play gain +1 resolve during the opponent's turn only.",
"icon_state": "borg_serv_suit",
"power": "1",
"resolve": "1",
"faction": "Service",
"summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Silicon",
+ "cardsubtype": "Silicon Bar",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "service_m"
@@ -1137,14 +1137,14 @@
"id": "stylinborg",
"name": "Cyborg (Service Shell- Ritzy)",
"desc": "Ooh, isn't this robot one cool cat?",
- "rules": "{$Asimov}",
+ "rules": "{$Asimov} On Play: Remove {$Asimov} from a creature.",
"icon_state": "borg_serv_tux",
"power": "1",
"resolve": "2",
"faction": "Service",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Silicon",
+ "cardsubtype": "Silicon Bar",
"rarity": "common",
"summon_icon_file": "icons/mob/silicon/robots.dmi",
"summon_icon_state": "tophat"
@@ -1153,14 +1153,14 @@
"id": "miner",
"name": "Shaft Miner",
"desc": "When the station needs materials, these are the guys who risk their lives, bravely pioneering the wastes of Indecipheres, to bring them in.",
- "rules": "Once per turn, you may pay 1 mana and tap this card: Draw one card from your deck, and either discard it, or send it to the bottom of your deck.",
+ "rules": "Tap: Look at the top 3 cards of your deck, choose one to add to your hand and shuffle the rest back into your deck.",
"icon_state": "miner",
"power": "5",
"resolve": "3",
"faction": "Cargo",
- "summoncost": "5",
+ "summoncost": "4",
"cardtype": "Creature",
- "cardsubtype": "Human Miner",
+ "cardsubtype": "Cargo",
"rarity": "rare",
"summon_icon_state": "Shaft Miner"
},
@@ -1168,14 +1168,14 @@
"id": "engi",
"name": "Station Engineer",
"desc": "Station Engineers maintain the intricate and delicate web of machinery that keeps you, and everyone else aboard your station, alive. No pressure there, then.",
- "rules": "Tap this card: Reduce the damage a card would take this turn from a battlefield to zero.",
+ "rules": "{$Graytide} Tap: Destroy or return to the hand a Battlefield card on the field.",
"icon_state": "engi",
"power": "2",
"resolve": "2",
"faction": "Engineering",
- "summoncost": "4",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Human Engineer",
+ "cardsubtype": "Atmospherics Maintenance",
"rarity": "common",
"summon_icon_state": "Station Engineer"
},
@@ -1183,14 +1183,14 @@
"id": "swarmer",
"name": "Swarmer",
"desc": "Leading researchers theorise that Swarmers were designed as some kind of vanguard for an alien invasion force, which seemingly has never materialised.",
- "rules": "{$Graytide}, {$Immunity} to Engineering creature cards.",
+ "rules": "{$Graytide}, {$Squad Tactics}: Create a 1/1 'Swarmer Spawn' token.",
"icon_state": "swarmer",
"power": "0",
"resolve": "1",
"faction": "Syndicate",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Robot",
+ "cardsubtype": "Silicon Maintenance",
"rarity": "rare",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
"summon_icon_state": "swarmer"
@@ -1199,14 +1199,14 @@
"id": "viro",
"name": "Virologist",
"desc": "Officially, the virologist is present on station to deal with novel diseases and ailments that originate from deep space. As everyone knows, this is not what the virologist actually does.",
- "rules": "",
+ "rules": "On Play: Gain +3 Power until the end of this turn.",
"icon_state": "viro",
- "power": "5",
+ "power": "1",
"resolve": "1",
"faction": "Medical",
- "summoncost": "3",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Doctor",
+ "cardsubtype": "Medical",
"rarity": "common",
"summon_icon_state": "Virologist"
},
@@ -1214,10 +1214,10 @@
"id": "warden",
"name": "Warden",
"desc": "The Warden is tasked with the herculean (and futile) feat of defending the armory and brig, and never leaving his post, no matter the situation.",
- "rules": "{$Squad Tactics}, {$Blocker}",
+ "rules": "{$Holy}, {$Blocker}",
"icon_state": "warden",
"power": "2",
- "resolve": "4",
+ "resolve": "6",
"faction": "Security",
"summoncost": "4",
"cardtype": "Creature",
@@ -1234,7 +1234,7 @@
"power": "2",
"resolve": "3",
"faction": "Xeno",
- "summoncost": "4",
+ "summoncost": "3",
"cardtype": "Creature",
"cardsubtype": "Xenomorph",
"series": "coreset2020",
@@ -1246,14 +1246,14 @@
"id": "tough_choices",
"name": "Tough Choices",
"desc": "Every Nanotrasen employee will, at some point, be forced to make a tough choice. Make sure you make the right one!",
- "rules": "Draw the top three cards from your deck. Summon one at no cost, and discard the other two.",
+ "rules": "Draw the top three cards from your deck. Choose one to add to your hand and discard the other two.",
"icon_state": "tough_choices",
"power": "0",
"resolve": "0",
"faction": "Syndicate",
- "summoncost": "2",
+ "summoncost": "1",
"cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardsubtype": "Security",
"series": "coreset2020",
"rarity": "common"
},
@@ -1261,14 +1261,14 @@
"id": "bsa_barrage",
"name": "Bluespace Barrage",
"desc": "The officers at Centcom are well known for their ability to hit targets extremely accurately with their bluespace artillery, especially when stupid pictures show up at their fax machine.",
- "rules": "Destroy any creature on the opponent's battlefield. If your opponent has no creatures, deal 2 damage directly to them.",
+ "rules": "The opponent chooses a creature they control. It is destroyed. If your opponent has no creatures, deal 5 damage directly to them.",
"icon_state": "bsa_barrage",
"power": "0",
"resolve": "0",
"faction": "Security",
- "summoncost": "3",
- "cardtype": "Event",
- "cardsubtype": "Instant",
+ "summoncost": "4",
+ "cardtype": "Instant",
+ "cardsubtype": "Command ERT",
"series": "coreset2020",
"rarity": "uncommon"
},
@@ -1282,8 +1282,8 @@
"resolve": "0",
"faction": "Science",
"summoncost": "1",
- "cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardtype": "Instant",
+ "cardsubtype": "Silicon",
"series": "coreset2020",
"rarity": "common"
},
@@ -1291,14 +1291,14 @@
"id": "adrenals",
"name": "Adrenals",
"desc": "A potent mixture of stimulants, designed to enhance a soldier's ability in the field. Technically illegal in Terragov territory, but since when has that stopped anyone?",
- "rules": "Grant +2/+1 to a Creature card that you control.",
+ "rules": "Grant +2/+1 to a Creature card that you control until the end of this turn.",
"icon_state": "adrenals",
"power": "+2",
"resolve": "+1",
"faction": "Medical",
"summoncost": "1",
- "cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardtype": "Instant",
+ "cardsubtype": "Medical Security",
"series": "coreset2020",
"rarity": "common"
},
@@ -1311,7 +1311,7 @@
"power": "0",
"resolve": "0",
"faction": "Engineering",
- "summoncost": "3",
+ "summoncost": "1",
"cardtype": "Battlefield",
"cardsubtype": "Atmospherics",
"series": "coreset2020",
@@ -1321,14 +1321,14 @@
"id": "psych",
"name": "Psychologist",
"desc": "The psychologist is the newest addition to Nanotrasen's medical workforce, quickly settling into their role as the job that does nothing valuable.",
- "rules": "",
+ "rules": "Tap: Destroy an equipment card on the field.",
"icon_state": "psych",
"power": "1",
"resolve": "1",
"faction": "Medical",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Human Doctor",
+ "cardsubtype": "Medical",
"series": "coreset2020",
"rarity": "common",
"summon_icon_state": "Psychologist"
@@ -1343,8 +1343,8 @@
"resolve": "0",
"faction": "Security",
"summoncost": "2",
- "cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardtype": "Instant",
+ "cardsubtype": "Security",
"series": "coreset2020",
"rarity": "uncommon"
},
@@ -1359,7 +1359,7 @@
"faction": "Science",
"summoncost": "3",
"cardtype": "Battlefield",
- "cardsubtype": "Anomaly",
+ "cardsubtype": "Wizardry Science",
"series": "coreset2020",
"rarity": "common"
},
@@ -1374,7 +1374,7 @@
"faction": "Science",
"summoncost": "3",
"cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardsubtype": "Science",
"series": "coreset2020",
"rarity": "uncommon"
},
@@ -1389,7 +1389,7 @@
"faction": "Cargo",
"summoncost": "2",
"cardtype": "Battlefield",
- "cardsubtype": "Event",
+ "cardsubtype": "Cargo",
"series": "coreset2020",
"rarity": "common"
},
@@ -1403,7 +1403,7 @@
"resolve": "0",
"faction": "Service",
"summoncost": "0",
- "cardtype": "Artifact",
+ "cardtype": "Equipment",
"cardsubtype": "Plant",
"series": "coreset2020",
"rarity": "legendary"
@@ -1419,7 +1419,7 @@
"faction": "Security",
"summoncost": "2",
"cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardsubtype": "Security Maintenance",
"series": "coreset2020",
"rarity": "common"
},
@@ -1432,9 +1432,9 @@
"power": "2",
"resolve": "3",
"faction": "Syndicate",
- "summoncost": "3",
+ "summoncost": "2",
"cardtype": "Creature",
- "cardsubtype": "Spirit",
+ "cardsubtype": "Wizardry",
"series": "coreset2020",
"rarity": "rare",
"summon_icon_file": "icons/mob/simple/mob.dmi",
@@ -1444,14 +1444,14 @@
"id": "re_education",
"name": "Re-Education",
"desc": "Nobody ever seems to return from re-education. Probably best not to question it.",
- "rules": "Destroy any creature on the opponent's battlefield.",
+ "rules": "Destroy a creature on the opponent's battlefield.",
"icon_state": "re_education",
"power": "0",
"resolve": "0",
"faction": "Security",
- "summoncost": "2",
- "cardtype": "Event",
- "cardsubtype": "Instant",
+ "summoncost": "5",
+ "cardtype": "Instant",
+ "cardsubtype": "Security",
"series": "coreset2020",
"rarity": "uncommon"
},
@@ -1459,14 +1459,14 @@
"id": "immoral_surgeon",
"name": "Immoral Surgeon",
"desc": "Remember, the Hippocratic oath is only a suggestion.",
- "rules": "2 Mana- You may tap Immoral Surgeon and give a creature +1/+1.",
+ "rules": "Tap: Give a creature +1/-1.",
"icon_state": "immoral_surgeon",
"power": "2",
- "resolve": "4",
+ "resolve": "1",
"faction": "Medical",
- "summoncost": "4",
+ "summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Lizard Doctor",
+ "cardsubtype": "Medical",
"series": "coreset2020",
"rarity": "uncommon",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
@@ -1476,14 +1476,14 @@
"id": "botanist_plant",
"name": "Committed Botanist",
"desc": "When you've grown the plants, nurtured the plants, and harvested the plants, there's only one place to go from there... becoming the plant.",
- "rules": "While Committed Botanist is on your battlefield, you can play Plant and Service cards at half their cost, rounded up.",
+ "rules": "While Committed Botanist is on your battlefield you can play Plant cards at half their cost, rounded up.",
"icon_state": "botanist_plant",
"power": "2",
"resolve": "3",
"faction": "Service",
"summoncost": "4",
"cardtype": "Creature",
- "cardsubtype": "Plant Worker",
+ "cardsubtype": "Plant Bar",
"series": "coreset2020",
"rarity": "rare",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
@@ -1493,14 +1493,14 @@
"id": "scientist_moth",
"name": "Scientist (Moth)",
"desc": "Moths are a common sight in Nanotrasen research departments, acting as integral ideas guys for new clothing designs and lighting innovations.",
- "rules": "",
+ "rules": "Enemy creatures cannot block this unit if it is attacking directly.",
"icon_state": "scientist_moth",
"power": "2",
"resolve": "2",
"faction": "Science",
"summoncost": "1",
"cardtype": "Creature",
- "cardsubtype": "Moth Scientist",
+ "cardsubtype": "Scientist",
"series": "coreset2020",
"rarity": "common",
"summon_icon_file": "icons/obj/toys/tcgsummons.dmi",
@@ -1510,14 +1510,14 @@
"id": "inducer",
"name": "Inducer",
"desc": "The inducer is a marvelous piece of tech, allowing the recharging of an internal cell without opening a machine.",
- "rules": "Pay 3 lifeshards: Gain 3 mana this turn.",
+ "rules": "Pay 2 lifeshards to gain 1 plasma.",
"icon_state": "inducer",
"power": "0",
"resolve": "0",
"faction": "Engineering",
"summoncost": "0",
"cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardsubtype": "Science",
"series": "coreset2020",
"rarity": "common"
},
@@ -1525,7 +1525,7 @@
"id": "fryer",
"name": "Deep Fryer",
"desc": "God bless the United States of Space America.",
- "rules": "For 2 mana: Tap this card and destroy an opposing equipment card.",
+ "rules": "Destroy an opposing equipment card.",
"icon_state": "fryer",
"power": "0",
"resolve": "0",
@@ -1540,14 +1540,14 @@
"id": "sleeping_carp",
"name": "Scroll of the Sleeping Carp",
"desc": "Created by the long-extinct Carp Monks of Space Tibet, the Sleeping Carp style has been kept alive by dedicated practitioners, and even found its way into the Syndicate's training regime.",
- "rules": "{$On Equip}: Your opponent must show you one card in their hand of their choice.",
+ "rules": "{$On Equip}: Your opponent must show you their entire hand.",
"icon_state": "sleeping_carp",
- "power": "3",
+ "power": "1",
"resolve": "1",
"faction": "Syndicate",
- "summoncost": "3",
+ "summoncost": "1",
"cardtype": "Equipment",
- "cardsubtype": "Weapon",
+ "cardsubtype": "Syndicate",
"series": "coreset2020",
"rarity": "epic"
},
@@ -1555,14 +1555,14 @@
"id": "nuclear_option",
"name": "The Nuclear Option",
"desc": "The Gorlex Marauders are well known for their nuclear weapons, and their nuke first, second, third and fourth policy with regards to deploying them.",
- "rules": "Destroy the active battlefield card. Deal 2 damage to all creatures on both battlefields.",
+ "rules": "Destroy the active battlefield card. Destroy all creatures on both battlefields.",
"icon_state": "nuclear_option",
"power": "0",
"resolve": "0",
"faction": "Syndicate",
- "summoncost": "3",
- "cardtype": "Event",
- "cardsubtype": "Instant",
+ "summoncost": "5",
+ "cardtype": "Instant",
+ "cardsubtype": "Atmospherics Syndicate",
"series": "coreset2020",
"rarity": "rare"
},
@@ -1570,14 +1570,14 @@
"id": "bepis",
"name": "B.E.P.I.S. Chamber",
"desc": "Created as an automated investment machine for a venture capitalism company, the B.E.P.I.S. ended up in the hands of Nanotrasen's research division after bankrupting the original creators... and 27 other corporations.",
- "rules": "Flip a coin. If heads, gain 2 mana. If tails, lose up to 2 mana.",
+ "rules": "Flip a coin. If heads, gain 4 mana.",
"icon_state": "bepis",
"power": "0",
"resolve": "0",
"faction": "Science",
- "summoncost": "0",
+ "summoncost": "2",
"cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardsubtype": "Clown Science",
"series": "coreset2020",
"rarity": "common"
},
@@ -1591,8 +1591,8 @@
"resolve": "0",
"faction": "Medical",
"summoncost": "3",
- "cardtype": "Event",
- "cardsubtype": "Instant",
+ "cardtype": "Instant",
+ "cardsubtype": "Clown Medical",
"series": "coreset2020",
"rarity": "uncommon"
},
@@ -1600,14 +1600,14 @@
"id": "disco_inferno",
"name": "Disco Inferno",
"desc": "(Burn baby burn) burn that mother down y'all\n(Burn baby burn) Disco Inferno\n(Burn baby burn) burn that mother down",
- "rules": "For 2 mana: Tap this card permanantly. While tapped, all active creatures take 3 damage during the first play phase of each turn. This card is destroyed after 2 turns of being tapped.",
+ "rules": "All active creatures take 3 damage during the first play phase of each turn. This card is destroyed after this effect triggers 4 times.",
"icon_state": "disco_inferno",
"power": "0",
"resolve": "0",
"faction": "Science",
"summoncost": "4",
"cardtype": "Battlefield",
- "cardsubtype": "Shuttle",
+ "cardsubtype": "Science Atmosian",
"series": "coreset2020",
"rarity": "uncommon"
}
diff --git a/strings/tcg/set_two.json b/strings/tcg/set_two.json
index 02331326deaee..935fbfaf840c1 100644
--- a/strings/tcg/set_two.json
+++ b/strings/tcg/set_two.json
@@ -1,6 +1,6 @@
{
"templates": [
- {
+ {
"template": "default",
"icon": "icons/runtime/tcg/xenos.dmi",
"series": "resinfront",
@@ -13,12 +13,12 @@
"id": "xenoborg",
"name": "Xenoborg",
"desc": "With a mini-gun in one hand and a rocket launcher in the other, the Xenoborg is a failed hybridization of a Xenomorph and a cyborg.",
- "rules": "{$Asimov}. Once per turn, you may sacrifice a silicon card, and pay the difference between that card's summon cost and this card's summon cost to summon this card from your hand.",
+ "rules": "You may sacrifice a silicon, reducing the cost of this card by the sacrificed creature's plasma cost, to summon this card from your hand.",
"icon_state": "xeno_borg",
"power": 7,
"resolve": 5,
"faction": "Science",
- "summoncost": 6,
+ "summoncost": 5,
"cardtype": "Creature",
"cardsubtype": "Silicon Xenomorph",
"rarity": "epic",
@@ -28,10 +28,10 @@
"id": "sentinel",
"name": "Xenomorph Sentinel",
"desc": "The juices from a Sentinel's neurotoxin gland pair brilliantly with a Pan-Galactic Gargle Blaster.",
- "rules": "{$Hivemind}",
+ "rules": "{$Hivemind} {$Taunt} {$Blocker}",
"icon_state": "xeno_sentinel",
"power": 5,
- "resolve": 3,
+ "resolve": 2,
"faction": "Xeno",
"summoncost": 4,
"cardtype": "Creature",
@@ -43,12 +43,12 @@
"id": "drone",
"name": "Xenomorph Drone",
"desc": "Rarely seen on the frontline, the Drone is your average worker that lays the foundation of the hive.",
- "rules": "{$Hivemind}. Tap this card: you may summon a Xeno faction creature for 1 less mana this turn.",
+ "rules": "{$Hivemind}. Tap: Summon a Xeno faction creature for 1 less mana.",
"icon_state": "xeno_drone",
"power": 1,
"resolve": 1,
"faction": "Xeno",
- "summoncost": 2,
+ "summoncost": 1,
"cardtype": "Creature",
"cardsubtype": "Xenomorph",
"rarity": "common",
@@ -63,7 +63,7 @@
"power": 6,
"resolve": 3,
"faction": "Xeno",
- "summoncost": 5,
+ "summoncost": 4,
"cardtype": "Creature",
"cardsubtype": "Xenomorph",
"rarity": "uncommon",
@@ -73,10 +73,10 @@
"id": "spitter",
"name": "Xenomorph Spitter",
"desc": "While their acid gland is too dangerous to mix with alcohol (not that it stops the marines), a Spitter's acid is useful for industry as it can melt almost anything with ease.",
- "rules": "{$Hivemind}. Tap this card: draw the top 2 cards of your deck, you may re-arrange their order, then return them to the top of your deck.",
+ "rules": "{$Hivemind}. Tap: Draw the top 2 cards of your deck then discard any that weren't Xenomorph.",
"icon_state": "xeno_spitter",
- "power": 3,
- "resolve": 3,
+ "power": 2,
+ "resolve": 1,
"faction": "Xeno",
"summoncost": 3,
"cardtype": "Creature",
@@ -103,12 +103,12 @@
"id": "praetorian",
"name": "Xenomorph Praetorian",
"desc": "The Praetorian is the Queen's royal guard, never seen far from the Queen's chambers.",
- "rules": "{$Hivemind}. If you have 2 or more other Xeno cards on your field alongside this card, you may sacrifice 3 Xeno cards and add Xenomorph Queen to your hand from your deck.",
+ "rules": "{$Hivemind}. {On Summon}: Add a Xenomorph Queen card to your hand from your deck.",
"icon_state": "xeno_praetorian",
- "power": 3,
- "resolve": 6,
+ "power": 2,
+ "resolve": 3,
"faction": "Xeno",
- "summoncost": 5,
+ "summoncost": 2,
"cardtype": "Creature",
"cardsubtype": "Xenomorph",
"rarity": "rare",
@@ -118,7 +118,7 @@
"id": "hivelord",
"name": "Xenomorph Hivelord",
"desc": "The Hivelord is the last word in construction, capable of building entire hives in a matter of seconds.",
- "rules": "{$Hivemind}. For two mana, tap this card and summon a 0/2 Resin Wall counter creature. Each Resin Wall has {$Blocker}.",
+ "rules": "{$Hivemind}. Tap; summon a 0/2 Resin Wall counter creature. Each Resin Wall has {$Blocker}.",
"icon_state": "xeno_hivelord",
"power": 1,
"resolve": 3,
@@ -133,7 +133,7 @@
"id": "boiler",
"name": "Xenomorph Boiler",
"desc": "The Boiler is a long-range artillery machine, capable of spewing clouds of acid that melt everything in seconds.",
- "rules": "{$Hivemind}. If this card attacks, it is tapped for an additional turn before it is untapped.",
+ "rules": "{$Hivemind}. This creatures' direct attacks cannot be blocked..",
"icon_state": "xeno_boiler",
"power": 6,
"resolve": 2,
@@ -150,7 +150,7 @@
"desc": "With large scythe claws for hands, the furious Ravager goes berserk at the sight of fire.",
"rules": "{$Hivemind}, {$Fury}",
"icon_state": "xeno_ravager",
- "power": 4,
+ "power": 2,
"resolve": 2,
"faction": "Xeno",
"summoncost": 3,
@@ -181,9 +181,9 @@
"rules": "{$Hivemind}, {$Blocker}",
"icon_state": "xeno_defender",
"power": 1,
- "resolve": 2,
+ "resolve": 1,
"faction": "Xeno",
- "summoncost": 2,
+ "summoncost": 0,
"cardtype": "Creature",
"cardsubtype": "Xenomorph",
"rarity": "common",
@@ -193,10 +193,10 @@
"id": "warrior",
"name": "Xenomorph Warrior",
"desc": "Warriors exhibit greater cruelty than other Xeno strains, enjoying snapping a victim's limbs before finishing them off.",
- "rules": "{$Hivemind}",
+ "rules": "{$Hivemind} {$First Strike}",
"icon_state": "xeno_warrior",
- "power": 4,
- "resolve": 4,
+ "power": 3,
+ "resolve": 3,
"faction": "Xeno",
"summoncost": 4,
"cardtype": "Creature",
@@ -208,12 +208,12 @@
"id": "queen",
"name": "Xenomorph Queen (Resin Frontier)",
"desc": "The ruler of the hive. Organs from a Queen fetch a high price amongst researchers and less-than-moral surgeons.",
- "rules": "For 2 mana, tap the equipped creature and summon a 1/1 Xenomorph Brood counter creature, with {$Hivemind}.",
+ "rules": "If the equipped card is a Xenomorph give it {$Taunt} {$First Strike} {$Blocker}.",
"icon_state": "xeno_queen",
- "power": 5,
- "resolve": 5,
+ "power": 0,
+ "resolve": 0,
"faction": "Xeno",
- "summoncost": 5,
+ "summoncost": 3,
"cardtype": "Equipment",
"cardsubtype": "Armour",
"rarity": "epic"
@@ -222,7 +222,7 @@
"id": "carrier",
"name": "Xenomorph Carrier",
"desc": "Carriers are like the Easter Bunny except the eggs they hide will kill you.",
- "rules": "{$Hivemind}, {$Squad Tactics}",
+ "rules": "{$Hivemind}, {$Deadeye}: Add one Xenomorph from your Deck to your hand.",
"icon_state": "xeno_carrier",
"power": 2,
"resolve": 2,
@@ -237,7 +237,7 @@
"id": "defiler",
"name": "Xenomorph Defiler",
"desc": "Instead of utilizing eggs, the Defiler prefers to inject an unknown chemical in their victim, causing a devastating infection.",
- "rules": "{$Hivemind}. When this card attacks a target enemy creature, calculate damage as though the target creature has this card's power subtracted from it first.",
+ "rules": "{$Hivemind}. While this creature is on the field all enemy creatures lose 1 power.",
"icon_state": "xeno_defiler",
"power": 1,
"resolve": 3,
@@ -255,7 +255,7 @@
"rules": "{$Hivemind}, {$Changeling}",
"icon_state": "xeno_predalien",
"power": 4,
- "resolve": 3,
+ "resolve": 2,
"faction": "Xeno",
"summoncost": 3,
"cardtype": "Creature",
@@ -267,12 +267,12 @@
"id": "shrike",
"name": "Xenomorph Shrike",
"desc": "It is unknown why Shrikes are able to lead a Hive, but their hives are always much smaller than a Queen's.",
- "rules": "{$Hivemind}. If Xenomorph Queen would be destroyed, you may re-equip Xenomorph Queen on this card instead, once per game.",
+ "rules": "{$Hivemind}. Tap: Add Xenomorph Queen from your discard pile to your hand.",
"icon_state": "xeno_shrike",
- "power": 3,
+ "power": 2,
"resolve": 1,
"faction": "Xeno",
- "summoncost": 2,
+ "summoncost": 1,
"cardtype": "Creature",
"cardsubtype": "Xenomorph",
"rarity": "rare",
@@ -297,7 +297,7 @@
"id": "hivemind",
"name": "Xenomorph Hivemind",
"desc": "Recently discovered to have sapience, this pulsating orb will dig into the earth and rapidly spread resin throughout planets.",
- "rules": "Defender",
+ "rules": "{$Blocker} Tap: Add 1 Xenomorph creature from your Deck to your hand.",
"icon_state": "xeno_hivemind",
"power": 0,
"resolve": 1,
@@ -312,7 +312,7 @@
"id": "screecher",
"name": "Xenomorph Screecher",
"desc": "The Screecher's screeches are more psychologically damaging than the resulting hearing damage.",
- "rules": "{$Deadeye}",
+ "rules": "{$Deadeye}: All tapped enemy creatures take 1 damage.",
"icon_state": "xeno_screecher",
"power": 3,
"resolve": 2,
@@ -327,12 +327,12 @@
"id": "creep",
"name": "Xenomorph Creep",
"desc": "This special Hunter strain prioritizes stalking its target. It evolves into a strain of Hunter that is (mercifully) rarely seen aboard space stations.",
- "rules": "Tap this card: Until the start of your next turn, this card has immunity to Xeno Creatures.",
+ "rules": "Tap this card: Until the start of your next turn, this card has immunity to creatures.",
"icon_state": "xeno_creep",
- "power": 4,
- "resolve": 3,
+ "power": 2,
+ "resolve": 2,
"faction": "Xeno",
- "summoncost": 5,
+ "summoncost": 1,
"cardtype": "Creature",
"cardsubtype": "Xenomorph",
"rarity": "common",
diff --git a/tgstation.dme b/tgstation.dme
index 5e42c1b3ed909..1c7351e27ad6d 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1062,6 +1062,7 @@
#include "code\datums\components\boomerang.dm"
#include "code\datums\components\boss_music.dm"
#include "code\datums\components\breeding.dm"
+#include "code\datums\components\bubble_icon_override.dm"
#include "code\datums\components\bullet_intercepting.dm"
#include "code\datums\components\bumpattack.dm"
#include "code\datums\components\burning.dm"
@@ -1776,6 +1777,7 @@
#include "code\datums\quirks\neutral_quirks\borg_ready.dm"
#include "code\datums\quirks\neutral_quirks\colorist.dm"
#include "code\datums\quirks\neutral_quirks\deviant_tastes.dm"
+#include "code\datums\quirks\neutral_quirks\evil.dm"
#include "code\datums\quirks\neutral_quirks\extrovert.dm"
#include "code\datums\quirks\neutral_quirks\foreigner.dm"
#include "code\datums\quirks\neutral_quirks\gamer.dm"
@@ -1917,6 +1919,7 @@
#include "code\datums\storage\subtypes\backpack.dm"
#include "code\datums\storage\subtypes\bag_of_holding.dm"
#include "code\datums\storage\subtypes\cards.dm"
+#include "code\datums\storage\subtypes\drone.dm"
#include "code\datums\storage\subtypes\duffel_bag.dm"
#include "code\datums\storage\subtypes\extract_inventory.dm"
#include "code\datums\storage\subtypes\fish_case.dm"
@@ -2162,12 +2165,14 @@
#include "code\game\machinery\computer\records\security.dm"
#include "code\game\machinery\dna_infuser\dna_infuser.dm"
#include "code\game\machinery\dna_infuser\dna_infusion.dm"
+#include "code\game\machinery\dna_infuser\infuser_actions.dm"
#include "code\game\machinery\dna_infuser\infuser_book.dm"
#include "code\game\machinery\dna_infuser\infuser_entry.dm"
#include "code\game\machinery\dna_infuser\infuser_entries\infuser_tier_one_entries.dm"
#include "code\game\machinery\dna_infuser\infuser_entries\infuser_tier_two_entries.dm"
#include "code\game\machinery\dna_infuser\infuser_entries\infuser_tier_zero_entries.dm"
#include "code\game\machinery\dna_infuser\organ_sets\carp_organs.dm"
+#include "code\game\machinery\dna_infuser\organ_sets\fish_organs.dm"
#include "code\game\machinery\dna_infuser\organ_sets\fly_organs.dm"
#include "code\game\machinery\dna_infuser\organ_sets\fox_organs.dm"
#include "code\game\machinery\dna_infuser\organ_sets\goliath_organs.dm"
@@ -2864,6 +2869,7 @@
#include "code\modules\actionspeed\modifiers\addiction.dm"
#include "code\modules\actionspeed\modifiers\base.dm"
#include "code\modules\actionspeed\modifiers\drugs.dm"
+#include "code\modules\actionspeed\modifiers\mobs.dm"
#include "code\modules\actionspeed\modifiers\mood.dm"
#include "code\modules\actionspeed\modifiers\status_effects.dm"
#include "code\modules\actionspeed\modifiers\wound.dm"
@@ -4505,8 +4511,9 @@
#include "code\modules\library\skill_learning\skill_station.dm"
#include "code\modules\library\skill_learning\skillchip.dm"
#include "code\modules\library\skill_learning\generic_skillchips\matrix_taunt.dm"
+#include "code\modules\library\skill_learning\generic_skillchips\misc.dm"
+#include "code\modules\library\skill_learning\generic_skillchips\musical.dm"
#include "code\modules\library\skill_learning\generic_skillchips\point.dm"
-#include "code\modules\library\skill_learning\generic_skillchips\rod_suplex.dm"
#include "code\modules\library\skill_learning\job_skillchips\_job.dm"
#include "code\modules\library\skill_learning\job_skillchips\chef.dm"
#include "code\modules\library\skill_learning\job_skillchips\clown.dm"
@@ -4514,6 +4521,7 @@
#include "code\modules\library\skill_learning\job_skillchips\janitor.dm"
#include "code\modules\library\skill_learning\job_skillchips\miner.dm"
#include "code\modules\library\skill_learning\job_skillchips\psychologist.dm"
+#include "code\modules\library\skill_learning\job_skillchips\research_director.dm"
#include "code\modules\library\skill_learning\job_skillchips\roboticist.dm"
#include "code\modules\library\skill_learning\job_skillchips\station_engineer.dm"
#include "code\modules\lighting\lighting_area.dm"
@@ -5660,10 +5668,10 @@
#include "code\modules\projectiles\projectile\special\ion.dm"
#include "code\modules\projectiles\projectile\special\meteor.dm"
#include "code\modules\projectiles\projectile\special\mindflayer.dm"
-#include "code\modules\projectiles\projectile\special\neurotoxin.dm"
#include "code\modules\projectiles\projectile\special\plasma.dm"
#include "code\modules\projectiles\projectile\special\rocket.dm"
#include "code\modules\projectiles\projectile\special\saboteur.dm"
+#include "code\modules\projectiles\projectile\special\spit.dm"
#include "code\modules\projectiles\projectile\special\temperature.dm"
#include "code\modules\projectiles\projectile\special\wormhole.dm"
#include "code\modules\reagents\chem_splash.dm"
diff --git a/tgui/packages/tgui-say/TguiSay.tsx b/tgui/packages/tgui-say/TguiSay.tsx
index fbee44f00f9e2..c06e72405d4f1 100644
--- a/tgui/packages/tgui-say/TguiSay.tsx
+++ b/tgui/packages/tgui-say/TguiSay.tsx
@@ -206,7 +206,8 @@ export class TguiSay extends Component<{}, State> {
// Is it a valid prefix?
const prefix = typed
.slice(0, 3)
- ?.toLowerCase() as keyof typeof RADIO_PREFIXES;
+ ?.toLowerCase()
+ ?.replace('.', ':') as keyof typeof RADIO_PREFIXES;
if (!RADIO_PREFIXES[prefix] || prefix === this.currentPrefix) {
return;
}