diff --git a/code/__DEFINES/dcs/flags.dm b/code/__DEFINES/dcs/flags.dm
index fb9dc00ddf3..3d045b12684 100644
--- a/code/__DEFINES/dcs/flags.dm
+++ b/code/__DEFINES/dcs/flags.dm
@@ -43,6 +43,8 @@
#define CALTROP_BYPASS_SHOES (1 << 0)
#define CALTROP_IGNORE_WALKERS (1 << 1)
#define CALTROP_SILENT (1 << 2)
+#define CALTROP_NOSTUN (1 << 3)
+#define CALTROP_NOCRAWL (1 << 4)
//Ingredient type in datum/component/customizable_reagent_holder
#define CUSTOM_INGREDIENT_TYPE_EDIBLE 1
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index aee782b58f1..c4152a4e130 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -114,6 +114,9 @@
/// Read the documentation for /datum/status_effect/confusion for more information.
#define STATUS_EFFECT_CONFUSION /datum/status_effect/confusion
+//Deals with covering the target in ants.
+#define STATUS_EFFECT_ANTS /datum/status_effect/ants
+
/////////////
// NEUTRAL //
/////////////
diff --git a/code/datums/components/food/decomposition.dm b/code/datums/components/food/decomposition.dm
new file mode 100644
index 00000000000..b81572b13d1
--- /dev/null
+++ b/code/datums/components/food/decomposition.dm
@@ -0,0 +1,147 @@
+//"Don't leave food on the floor, that's how we get ants"
+
+#define DECOMPOSITION_TIME 10 MINUTES
+#define DECOMPOSITION_TIME_RAW 5 MINUTES
+#define DECOMPOSITION_TIME_GROSS 7 MINUTES
+
+#define DECOMP_EXAM_NORMAL 0
+#define DECOMP_EXAM_GROSS 1
+#define DECOMP_EXAM_RAW 2
+
+/datum/component/decomposition
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// Makes sure food only starts decomposing if a player's EVER picked it up before
+ var/handled = FALSE
+ /// Used to stop food in someone's hand & in storage slots from decomposing.
+ var/protected = FALSE
+ /// Used to stop the timer & check for the examine proc
+ var/timerid
+ /// Used so the timer won't reset.
+ var/time_remaining = DECOMPOSITION_TIME
+ /// Used to give raw/gross food lower timers
+ var/decomp_flags
+ /// Used for examining
+ var/examine_type = DECOMP_EXAM_NORMAL
+
+/datum/component/decomposition/Initialize(decomp_flags = NONE)
+ if(!isobj(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.decomp_flags = decomp_flags
+
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/handle_movement)
+ RegisterSignal(parent, list(
+ COMSIG_ITEM_PICKUP, //person picks up an item
+ COMSIG_STORAGE_ENTERED), //Object enters a storage object (boxes, etc.)
+ .proc/picked_up)
+ RegisterSignal(parent, list(
+ COMSIG_ITEM_DROPPED, //Object is dropped anywhere
+ COMSIG_STORAGE_EXITED), //Object exits a storage object (boxes, etc)
+ .proc/dropped)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
+
+ if(decomp_flags & RAW) // Raw food overrides gross
+ time_remaining = DECOMPOSITION_TIME_RAW
+ examine_type = DECOMP_EXAM_RAW
+ else if(decomp_flags & GROSS)
+ time_remaining = DECOMPOSITION_TIME_GROSS
+ examine_type = DECOMP_EXAM_GROSS
+
+/datum/component/decomposition/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_ITEM_PICKUP,
+ COMSIG_STORAGE_ENTERED,
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_ITEM_DROPPED,
+ COMSIG_STORAGE_EXITED,
+ COMSIG_PARENT_EXAMINE))
+
+/datum/component/decomposition/proc/handle_movement()
+ if(!handled) // Has someone touched this previously?
+ return
+ var/obj/food = parent // Doesn't HAVE to be food, that's just what it's intended for
+
+ if(!(istype(food.loc, /turf/open))) // Is this currently in an open turf?
+ remove_timer() // If not, remove any active timers and return
+ return
+ if(locate(/obj/structure/table) in get_turf(food)) // Is this currently over a table?
+ remove_timer()
+ return
+ if(locate(/obj/structure/rack) in get_turf(food)) // Is it on a rack?
+ remove_timer()
+ return
+ if(locate(/obj/machinery/conveyor) in get_turf(food)) // Makes sure no decals spawn on disposals conveyors
+ remove_timer()
+ return
+ // If all other checks fail, then begin decomposition.
+ timerid = addtimer(CALLBACK(src, .proc/decompose), time_remaining, TIMER_STOPPABLE | TIMER_UNIQUE)
+
+/datum/component/decomposition/Destroy()
+ remove_timer()
+ return ..()
+
+/datum/component/decomposition/proc/remove_timer()
+ if(active_timers) // Makes sure there's an active timer to delete.
+ time_remaining = timeleft(timerid)
+ deltimer(timerid)
+
+/datum/component/decomposition/proc/dropped()
+ SIGNAL_HANDLER
+ protected = FALSE
+ handle_movement()
+
+/datum/component/decomposition/proc/picked_up()
+ SIGNAL_HANDLER
+ remove_timer()
+ protected = TRUE
+ if(!handled)
+ handled = TRUE
+
+/datum/component/decomposition/proc/decompose()
+ var/obj/decomp = parent //Lets us spawn things at decomp
+ new /obj/effect/decal/cleanable/ants(decomp.loc)
+ new /obj/item/food/badrecipe/moldy(decomp.loc)
+ decomp.visible_message("[decomp] gets overtaken by mold and ants! Gross!")
+ qdel(decomp)
+ return
+
+/datum/component/decomposition/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ var/time_d = 0
+ if(active_timers) // Is the timer currently applied to this?
+ time_d = timeleft(timerid)
+ else
+ time_d = time_remaining
+ switch(examine_type)
+ if(DECOMP_EXAM_NORMAL)// All other types
+ switch(time_d) // Deciseconds used so there's no gaps between examine times.
+ if(3001 to 4500) // 7.5 to 5 Minutes left
+ examine_list += "[parent] looks kinda stale."
+ if(1501 to 3000) // 5 to 2.5 Minutes left
+ examine_list += "[parent] is starting to look pretty gross."
+ if(1 to 1500) // 2.5 Minutes to 1 Decisecond left
+ examine_list += "[parent] looks barely edible."
+ if(DECOMP_EXAM_GROSS) // Gross food
+ switch(time_d)
+ if(2101 to 3150) // 5.25 to 3.5 Minutes
+ examine_list += "[parent] looks kinda stale."
+ if(1050 to 2100) // 3.5 to 1.75 Minutes left
+ examine_list += "[parent] is starting to look pretty gross."
+ if(1 to 1051) // 1.75 Minutes to 1 Decisecond left
+ examine_list += "[parent] looks barely edible."
+ if(DECOMP_EXAM_RAW) // Raw food
+ switch(time_d)
+ if(1501 to 2250) // 3.75 to 2.5 Minutes left
+ examine_list += "[parent] looks kinda stale."
+ if(751 to 1500) // 2.5 to 1.25 Minutes left
+ examine_list += "[parent] is starting to look pretty gross."
+ if(1 to 750) // 1.25 Minutes to 1 Decisecond left
+ examine_list += "[parent] looks barely edible."
+
+#undef DECOMPOSITION_TIME
+#undef DECOMPOSITION_TIME_GROSS
+#undef DECOMPOSITION_TIME_RAW
+
+#undef DECOMP_EXAM_NORMAL
+#undef DECOMP_EXAM_GROSS
+#undef DECOMP_EXAM_RAW
diff --git a/code/datums/elements/caltrop.dm b/code/datums/elements/caltrop.dm
index 2118743b458..4b6078f8979 100644
--- a/code/datums/elements/caltrop.dm
+++ b/code/datums/elements/caltrop.dm
@@ -19,12 +19,15 @@
///Miscelanous caltrop flags; shoe bypassing, walking interaction, silence
var/flags
+ ///The sound that plays when a caltrop is triggered.
+ var/soundfile
+
///given to connect_loc to listen for something moving over target
var/static/list/crossed_connections = list(
COMSIG_ATOM_ENTERED = .proc/on_entered,
)
-/datum/element/caltrop/Attach(datum/target, min_damage = 0, max_damage = 0, probability = 100, flags = NONE)
+/datum/element/caltrop/Attach(datum/target, min_damage = 0, max_damage = 0, probability = 100, flags = NONE, soundfile = null)
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
@@ -33,6 +36,7 @@
src.max_damage = max(min_damage, max_damage)
src.probability = probability
src.flags = flags
+ src.soundfile = soundfile
if(ismovable(target))
AddElement(/datum/element/connect_loc_behalf, target, crossed_connections)
@@ -62,7 +66,7 @@
if(H.buckled) //if they're buckled to something, that something should be checked instead.
return
- if(H.body_position == LYING_DOWN) //if we're not standing we cant step on the caltrop
+ if(H.body_position == LYING_DOWN && !(flags & CALTROP_NOCRAWL)) //if we're not standing we cant step on the caltrop
return
var/picked_def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
@@ -94,7 +98,13 @@
)
H.apply_damage(damage, BRUTE, picked_def_zone, wound_bonus = CANT_WOUND)
- H.Paralyze(60)
+
+ if(!(flags & CALTROP_NOSTUN)) // Won't set off the paralysis.
+ H.Paralyze(60)
+
+ if(!soundfile)
+ return
+ playsound(H, soundfile, 15, TRUE, -3)
/datum/element/caltrop/Detach(datum/target)
. = ..()
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 7943e341197..1f922008a4a 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -969,6 +969,91 @@
. = ..()
QDEL_NULL(mob_overlay)
+//Deals with ants covering someone.
+/datum/status_effect/ants
+ id = "ants"
+ status_type = STATUS_EFFECT_REFRESH
+ alert_type = /atom/movable/screen/alert/status_effect/ants
+ duration = 2 MINUTES //Keeping the normal timer makes sure people can't somehow dump 300+ ants on someone at once so they stay there for like 30 minutes. Max w/ 1 dump is 57.6 brute.
+ examine_text = "SUBJECTPRONOUN is covered in ants!"
+ processing_speed = STATUS_EFFECT_NORMAL_PROCESS
+ /// Will act as the main timer as well as changing how much damage the ants do.
+ var/ants_remaining = 0
+
+/datum/status_effect/ants/on_creation(mob/living/new_owner, amount_left)
+ if(isnum(amount_left))
+ to_chat(new_owner, "You're covered in ants!")
+ ants_remaining += amount_left
+ . = ..()
+
+/datum/status_effect/ants/refresh(effect, amount_left)
+ var/mob/living/carbon/human/victim = owner
+ if(isnum(amount_left) && ants_remaining >= 1)
+ if(!prob(1)) // 99%
+ to_chat(victim, "You're covered in MORE ants!")
+ else // 1%
+ victim.say("AAHH! THIS SITUATION HAS ONLY BEEN MADE WORSE WITH THE ADDITION OF YET MORE ANTS!!", forced = /datum/status_effect/ants)
+ ants_remaining += amount_left
+ . = ..()
+
+/datum/status_effect/ants/on_remove()
+ ants_remaining = 0
+ to_chat(owner, "All of the ants are off of your body!")
+ UnregisterSignal(owner, COMSIG_COMPONENT_CLEAN_ACT, .proc/ants_washed)
+ . = ..()
+
+/datum/status_effect/ants/proc/ants_washed()
+ SIGNAL_HANDLER
+ owner.remove_status_effect(STATUS_EFFECT_ANTS)
+ return COMPONENT_CLEANED
+
+/datum/status_effect/ants/tick()
+ var/mob/living/carbon/human/victim = owner
+ victim.adjustBruteLoss(max(0.1, round((ants_remaining * 0.004),0.1))) //Scales with # of ants (lowers with time). Roughly 10 brute over 50 seconds.
+ if(victim.stat <= SOFT_CRIT) //Makes sure people don't scratch at themselves while they're unconcious
+ if(prob(15))
+ switch(rand(1,2))
+ if(1)
+ victim.say(pick("GET THEM OFF ME!!", "OH GOD THE ANTS!!", "MAKE IT END!!", "THEY'RE EVERYWHERE!!", "GET THEM OFF!!", "SOMEBODY HELP ME!!"), forced = /datum/status_effect/ants)
+ if(2)
+ victim.emote("scream")
+ if(prob(50))
+ switch(rand(1,50))
+ if (1 to 8) //16% Chance
+ var/obj/item/bodypart/head/hed = victim.get_bodypart(BODY_ZONE_HEAD)
+ to_chat(victim, "You scratch at the ants on your scalp!.")
+ hed.receive_damage(0.1,0)
+ if (9 to 29) //40% chance
+ var/obj/item/bodypart/arm = victim.get_bodypart(pick(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM))
+ to_chat(victim, "You scratch at the ants on your arms!")
+ arm.receive_damage(0.1,0)
+ if (30 to 49) //38% chance
+ var/obj/item/bodypart/leg = victim.get_bodypart(pick(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG))
+ to_chat(victim, "You scratch at the ants on your leg!")
+ leg.receive_damage(0.1,0)
+ if(50) // 2% chance
+ to_chat(victim, "You rub some ants away from your eyes!")
+ victim.blur_eyes(3)
+ ants_remaining -= 5 // To balance out the blindness, it'll be a little shorter.
+ ants_remaining--
+ if(ants_remaining <= 0 || victim.stat >= HARD_CRIT)
+ victim.remove_status_effect(STATUS_EFFECT_ANTS) //If this person has no more ants on them or are dead, they are no longer affected.
+
+/atom/movable/screen/alert/status_effect/ants
+ name = "Ants!"
+ desc = "JESUS FUCKING CHRIST! CLICK TO GET THOSE THINGS OFF!"
+ icon_state = "antalert"
+
+/atom/movable/screen/alert/status_effect/ants/Click()
+ var/mob/living/living = owner
+ if(!istype(living) || !living.can_resist() || living != owner)
+ return
+ to_chat(living, "You start to shake the ants off!")
+ if(!do_after(living, 2 SECONDS, target = living))
+ return
+ for (var/datum/status_effect/ants/ant_covered in living.status_effects)
+ to_chat(living, "You manage to get some of the ants off!")
+ ant_covered.ants_remaining -= 10 // 5 Times more ants removed per second than just waiting in place
/datum/status_effect/ghoul
id = "ghoul"
@@ -980,4 +1065,4 @@
name = "Flesh Servant"
desc = "You are a Ghoul! A eldritch monster reanimated to serve its master."
icon_state = "mind_control"
-
+
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index 52eeb0f795b..5fd836948ae 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -78,7 +78,7 @@
/datum/status_effect/proc/before_remove() //! Called before being removed; returning FALSE will cancel removal
return TRUE
-/datum/status_effect/proc/refresh()
+/datum/status_effect/proc/refresh(effect, ...)
var/original_duration = initial(duration)
if(original_duration == -1)
return
@@ -112,17 +112,17 @@
. = FALSE
var/datum/status_effect/S1 = effect
LAZYINITLIST(status_effects)
+ var/list/arguments = args.Copy()
+ arguments[1] = src
for(var/datum/status_effect/S in status_effects)
if(S.id == initial(S1.id) && S.status_type)
if(S.status_type == STATUS_EFFECT_REPLACE)
S.be_replaced()
else if(S.status_type == STATUS_EFFECT_REFRESH)
- S.refresh()
+ S.refresh(arglist(arguments))
return
else
return
- var/list/arguments = args.Copy()
- arguments[1] = src
S1 = new effect(arguments)
. = S1
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index bab9e30cd01..e79706459ac 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -257,3 +257,20 @@
/obj/effect/decal/cleanable/garbage/Initialize()
. = ..()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLUDGE, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 15)
+
+/obj/effect/decal/cleanable/ants
+ name = "space ants"
+ desc = "A small colony of space ants. They're normally used to the vacuum of space, so they can't climb too well."
+ icon = 'icons/obj/objects.dmi'
+ icon_state = "spaceants"
+ beauty = -150
+
+/obj/effect/decal/cleanable/ants/Initialize(mapload)
+ . = ..()
+ var/scale = (rand(6, 8) / 10) + (rand(2, 5) / 50)
+ transform = matrix(transform, scale, scale, MATRIX_SCALE)
+ setDir(pick(GLOB.cardinals))
+ reagents.add_reagent(/datum/reagent/ants, rand(2, 5))
+ pixel_x = rand(-5, 5)
+ pixel_y = rand(-5, 5)
+ AddElement(/datum/element/caltrop, min_damage = 0.2, max_damage = 1, flags = (CALTROP_NOCRAWL | CALTROP_NOSTUN | CALTROP_BYPASS_SHOES), soundfile = 'sound/weapons/bite.ogg')
diff --git a/code/game/objects/effects/spawners/decals.dm b/code/game/objects/effects/spawners/decals.dm
index f8f2d19d74a..b97e1c0c619 100644
--- a/code/game/objects/effects/spawners/decals.dm
+++ b/code/game/objects/effects/spawners/decals.dm
@@ -11,5 +11,6 @@
/obj/effect/decal/cleanable/wrapping = 5,
/obj/effect/decal/cleanable/plastic = 5,
/obj/effect/decal/cleanable/glass = 5,
- /obj/effect/decal/cleanable/dirt = 30
+ /obj/effect/decal/cleanable/dirt = 30,
+ /obj/effect/decal/cleanable/ants = 5,
)
diff --git a/code/game/objects/items/food/_food.dm b/code/game/objects/items/food/_food.dm
index 5c21b664d10..6e167cc380e 100644
--- a/code/game/objects/items/food/_food.dm
+++ b/code/game/objects/items/food/_food.dm
@@ -36,6 +36,8 @@
var/burns_on_grill = FALSE
///Price of this food if sold in a venue
var/venue_value
+ ///Food that's immune to decomposition.
+ var/preserved_food = FALSE
/obj/item/food/Initialize()
. = ..()
@@ -51,6 +53,7 @@
MakeProcessable()
MakeLeaveTrash()
MakeGrillable()
+ MakeDecompose()
///This proc adds the edible component, overwrite this if you for some reason want to change some specific args like callbacks.
/obj/item/food/proc/MakeEdible()
@@ -82,3 +85,8 @@
if(trash_type)
AddElement(/datum/element/food_trash, trash_type)
return
+
+///This proc makes things decompose. Set preserved_food to TRUE to make it never decompose.
+/obj/item/food/proc/MakeDecompose()
+ if(!preserved_food)
+ AddComponent(/datum/component/decomposition, decomp_flags = foodtypes)
diff --git a/code/game/objects/items/food/misc.dm b/code/game/objects/items/food/misc.dm
index b45ca028fc4..0ea1795de9e 100644
--- a/code/game/objects/items/food/misc.dm
+++ b/code/game/objects/items/food/misc.dm
@@ -190,6 +190,7 @@
food_reagents = list(/datum/reagent/toxin/bad_food = 30)
foodtypes = GROSS
w_class = WEIGHT_CLASS_SMALL
+ preserved_food = TRUE //Can't decompose any more than this
/obj/item/food/badrecipe/Initialize()
. = ..()
@@ -197,7 +198,7 @@
/obj/item/food/badrecipe/moldy
name = "moldy mess"
- desc = "A rancid, living culture of mold. Somewhere, under there, at SOME POINT... there was food."
+ desc = "A rancid, disgusting culture of mold and ants. Somewhere under there, at some point, there was food."
food_reagents = list(/datum/reagent/consumable/mold = 30)
/obj/item/food/badrecipe/moldy/Initialize()
@@ -592,12 +593,15 @@
w_class = WEIGHT_CLASS_NORMAL
max_volume = 30
w_class = WEIGHT_CLASS_SMALL
+ preserved_food = TRUE
/obj/item/food/canned/proc/open_can(mob/user)
to_chat(user, span_notice("You pull back the tab of \the [src]."))
playsound(user.loc, 'sound/items/foodcanopen.ogg', 50)
reagents.flags |= OPENCONTAINER
+ preserved_food = FALSE
+ MakeDecompose()
/obj/item/food/canned/attack_self(mob/user)
if(!is_drainable())
@@ -691,3 +695,13 @@
/obj/item/food/rationpack/proc/check_liked(fraction, mob/M) //Nobody likes rationpacks. Nobody.
return FOOD_DISLIKED
+
+/obj/item/food/ant_candy
+ name = "ant candy"
+ desc = "A colony of ants suspended in hardened sugar. Those things are dead, right?"
+ icon_state = "ant_pop"
+ food_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1, /datum/reagent/consumable/sugar = 5, /datum/reagent/ants = 3)
+ tastes = list("candy" = 1, "insects" = 1)
+ foodtypes = JUNKFOOD | SUGAR | GROSS
+ food_flags = FOOD_FINGER_FOOD
+ w_class = WEIGHT_CLASS_TINY
diff --git a/code/game/objects/items/food/pizza.dm b/code/game/objects/items/food/pizza.dm
index afa9c7b65b5..2236b6f7ff6 100644
--- a/code/game/objects/items/food/pizza.dm
+++ b/code/game/objects/items/food/pizza.dm
@@ -252,3 +252,21 @@
/obj/item/food/pizzaslice/arnold/attackby(obj/item/I, mob/user)
i_kill_you(I, user)
. = ..()
+
+// Ant Pizza, now with more ants.
+/obj/item/food/pizza/ants
+ name = "\improper Ant Party pizza"
+ desc = "/// Filled with bugs, remember to fix"
+ icon_state = "antpizza"
+ food_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/ants = 25, /datum/reagent/consumable/tomatojuice = 10, /datum/reagent/consumable/nutriment/vitamin = 4, /datum/reagent/consumable/nutriment/protein = 2)
+ tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "insects" = 1)
+ foodtypes = GRAIN | VEGETABLES | DAIRY | GROSS
+ slice_type = /obj/item/food/pizzaslice/ants
+ boxtag = "Anthill Deluxe"
+
+/obj/item/food/pizzaslice/ants
+ name = "\improper Ant Party pizza slice"
+ desc = "The key to a perfect slice of pizza is not to overdo it with the ants."
+ icon_state = "antpizzaslice"
+ tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "insects" = 1)
+ foodtypes = GRAIN | VEGETABLES | DAIRY | GROSS
diff --git a/code/game/turfs/open/dirtystation.dm b/code/game/turfs/open/dirtystation.dm
index dc1f36c0c9e..857057d9d35 100644
--- a/code/game/turfs/open/dirtystation.dm
+++ b/code/game/turfs/open/dirtystation.dm
@@ -37,7 +37,7 @@
if(prob(80)) //mid dirt - 1/15
return
- //Construction zones. Blood, sweat, and oil. Oh, and dirt.
+ //Construction zones. Blood, sweat, and oil. Oh, and dirt. A small colony of space-ants or two will pop up
var/static/list/engine_dirt_areas = typecacheof(list(/area/engineering,
/area/command/heads_quarters/ce,
/area/science/robotics,
@@ -49,13 +49,16 @@
if(prob(3))
new /obj/effect/decal/cleanable/blood/old(src)
else
- if(prob(35))
- if(prob(4))
- new /obj/effect/decal/cleanable/robot_debris/old(src)
- else
- new /obj/effect/decal/cleanable/oil(src)
- else
- new /obj/effect/decal/cleanable/dirt(src)
+ switch (rand(1, 100))
+ if(1 to 35)//35%
+ if(prob(4))
+ new /obj/effect/decal/cleanable/robot_debris/old(src)
+ else
+ new /obj/effect/decal/cleanable/oil(src)
+ if(35 to 95)//60%
+ new /obj/effect/decal/cleanable/dirt(src)
+ if(95 to 100)//5%
+ new /obj/effect/decal/cleanable/ants(src)
return
//Bathrooms. Blood, vomit, and shavings in the sinks.
@@ -67,6 +70,8 @@
new /obj/effect/decal/cleanable/vomit/old(src)
else
new /obj/effect/decal/cleanable/blood/old(src)
+ else if(prob(40)) //16% effective chance
+ new /obj/effect/decal/cleanable/ants(src)
return
// Cargo bays covered in oil.
@@ -95,7 +100,7 @@
new /obj/effect/decal/cleanable/blood/old(src)
return
- //Kitchen areas. Broken eggs, flour, spilled milk (no crying allowed.)
+ //Kitchen areas. Broken eggs, flour, spilled milk (no crying allowed.), ants.
var/static/list/kitchen_dirt_areas = typecacheof(list(/area/service/kitchen,
/area/service/cafeteria))
if(is_type_in_typecache(A, kitchen_dirt_areas))
@@ -104,6 +109,8 @@
new /obj/effect/decal/cleanable/food/egg_smudge(src)
else
new /obj/effect/decal/cleanable/food/flour(src)
+ else if(prob(20)) //12% effective chance
+ new /obj/effect/decal/cleanable/ants(src)
return
//Medical areas. Mostly clean by space-OSHA standards, but has some blood and oil spread about.
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
index e017ff6aadf..ef6c5f27277 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
@@ -193,3 +193,13 @@
)
result = /obj/item/food/cheese/royal
subcategory = CAT_MISCFOOD
+
+/datum/crafting_recipe/food/ant_candy
+ name = "Ant Candy"
+ reqs = list(/obj/item/stack/rods = 1,
+ /datum/reagent/consumable/sugar = 5,
+ /datum/reagent/water = 5,
+ /datum/reagent/ants = 10
+ )
+ result = /obj/item/food/ant_candy
+ subcategory = CAT_MISCFOOD
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm
index 13def8f7347..05f58de4123 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm
@@ -101,3 +101,14 @@
)
result = /obj/item/food/pizza/pineapple
subcategory = CAT_PIZZA
+
+/datum/crafting_recipe/food/antspizza
+ name = "Ant Party pizza"
+ reqs = list(
+ /obj/item/food/pizzabread = 1,
+ /obj/item/food/cheese = 2,
+ /obj/item/food/grown/tomato = 1,
+ /datum/reagent/ants = 20
+ )
+ result = /obj/item/food/pizza/ants
+ subcategory = CAT_PIZZA
diff --git a/code/modules/mob/living/simple_animal/friendly/ant.dm b/code/modules/mob/living/simple_animal/friendly/ant.dm
new file mode 100644
index 00000000000..3e246ec4a02
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/friendly/ant.dm
@@ -0,0 +1,50 @@
+/mob/living/simple_animal/ant
+ name = "giant ant"
+ desc = "A writhing mass of ants, glued together to make an adorable pet!"
+ icon = 'icons/mob/pets.dmi'
+ icon_state = "ant"
+ icon_living = "ant"
+ icon_dead = "ant_dead"
+ speak = list("BZZZZT!", "CHTCHTCHT!", "Bzzz", "ChtChtCht")
+ speak_emote = list("buzzes", "chitters")
+ emote_hear = list("buzzes.", "clacks.")
+ emote_see = list("shakes its head.", "twitches its antennae.")
+ speak_chance = 1
+ turns_per_move = 5
+ see_in_dark = 6
+ pass_flags = PASSTABLE
+ mob_size = MOB_SIZE_SMALL
+ mob_biotypes = MOB_ORGANIC|MOB_BUG
+ minbodytemp = 200
+ maxbodytemp = 400
+ unsuitable_atmos_damage = 1
+ butcher_results = list(/obj/item/food/meat/slab = 2, /obj/effect/decal/cleanable/ants = 1) //It's just a bunch of ants glued together into a larger ant
+ response_help_continuous = "pets"
+ response_help_simple = "pet"
+ response_disarm_continuous = "gently pushes aside"
+ response_disarm_simple = "gently push aside"
+ response_harm_continuous = "kicks"
+ response_harm_simple = "kick"
+ gold_core_spawnable = FRIENDLY_SPAWN
+ can_be_held = FALSE
+ footstep_type = FOOTSTEP_MOB_CLAW
+ health = 75
+ maxHealth = 75
+ // randomizes hunting intervals, minimum 5 turns
+ var/time_to_hunt = 5
+
+/mob/living/simple_animal/ant/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
+ time_to_hunt = rand(5,10)
+
+/mob/living/simple_animal/ant/Life(delta_time = SSMOBS_DT, times_fired) // In this larger state, the ants have become the predators.
+ . = ..()
+ turns_since_scan++
+ if(turns_since_scan > time_to_hunt)
+ turns_since_scan = 0
+ var/list/target_types = list(/mob/living/simple_animal/hostile/cockroach)
+ for(var/mob/living/simple_animal/hostile/potential_target in view(2, get_turf(src)))
+ if(potential_target.type in target_types)
+ hunt(potential_target)
+ return
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 7cd350c7e6f..5250eee3f27 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -29,12 +29,7 @@
response_harm_continuous = "kicks"
response_harm_simple = "kick"
mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT
- var/turns_since_scan = 0
var/mob/living/simple_animal/mouse/movement_target
- ///Limits how often cats can spam chasing mice.
- COOLDOWN_DECLARE(emote_cooldown)
- ///Can this cat catch special mice?
- var/inept_hunter = FALSE
gold_core_spawnable = FRIENDLY_SPAWN
collar_type = "cat"
can_be_held = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/cockroach.dm b/code/modules/mob/living/simple_animal/hostile/cockroach.dm
index 49508e5465e..f44f9843068 100644
--- a/code/modules/mob/living/simple_animal/hostile/cockroach.dm
+++ b/code/modules/mob/living/simple_animal/hostile/cockroach.dm
@@ -31,13 +31,16 @@
environment_smash = ENVIRONMENT_SMASH_NONE
faction = list("neutral")
var/squish_chance = 50
+ // Randomizes hunting intervals, minumum 5 turns
+ var/time_to_hunt = 5
+
/mob/living/simple_animal/hostile/cockroach/Initialize()
. = ..()
add_cell_sample()
make_squashable()
-
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
+ time_to_hunt = rand(5,10)
/mob/living/simple_animal/hostile/cockroach/proc/make_squashable()
AddComponent(/datum/component/squashable, squash_chance = 50, squash_damage = 1)
@@ -45,6 +48,18 @@
/mob/living/simple_animal/hostile/cockroach/add_cell_sample()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_COCKROACH, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 7)
+/mob/living/simple_animal/hostile/cockroach/Life(delta_time = SSMOBS_DT, times_fired) // Cockroaches are predators to space ants
+ . = ..()
+ turns_since_scan++
+ if(turns_since_scan > time_to_hunt)
+ turns_since_scan = 0
+ var/list/target_types = list(/obj/effect/decal/cleanable/ants)
+ for(var/obj/effect/decal/cleanable/ants/potential_target in view(2, get_turf(src)))
+ if(potential_target.type in target_types)
+ hunt(potential_target)
+ return
+
+
/obj/projectile/glockroachbullet
damage = 10 //same damage as a hivebot
damage_type = BRUTE
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 882746c6555..2fd550b0fbc 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -156,6 +156,13 @@
///Generic flags
var/simple_mob_flags = NONE
+ ///Limits how often mobs can hunt other mobs
+ COOLDOWN_DECLARE(emote_cooldown)
+ var/turns_since_scan = 0
+
+ ///Is this animal horrible at hunting?
+ var/inept_hunter = FALSE
+
/mob/living/simple_animal/Initialize(mapload)
. = ..()
@@ -684,3 +691,41 @@
/mob/living/simple_animal/proc/stop_deadchat_plays()
stop_automated_movement = FALSE
+
+
+//Makes this mob hunt the prey, be it living or an object. Will kill living creatures, and delete objects.
+/mob/living/simple_animal/proc/hunt(hunted)
+ if(src == hunted) //Make sure it doesn't eat itself. While not likely to ever happen, might as well check just in case.
+ return
+ stop_automated_movement = FALSE
+ if(!isturf(src.loc)) // Are we on a proper turf?
+ return
+ if(stat || resting || buckled) // Are we concious, upright, and not buckled?
+ return
+ if(!COOLDOWN_FINISHED(src, emote_cooldown)) // Has the cooldown on this ended?
+ return
+ if(!Adjacent(hunted))
+ stop_automated_movement = TRUE
+ walk_to(src,hunted,0,3)
+ if(Adjacent(hunted))
+ hunt(hunted) // In case it gets next to the target immediately, skip the scan timer and kill it.
+ return
+ if(isliving(hunted)) // Are we hunting a living mob?
+ var/mob/living/prey = hunted
+ if(inept_hunter) // Make your hunter inept to have them unable to catch their prey.
+ visible_message("[src] chases [prey] around, to no avail!")
+ step(prey, pick(GLOB.cardinals))
+ COOLDOWN_START(src, emote_cooldown, 1 MINUTES)
+ return
+ if(!(prey.stat))
+ manual_emote("chomps [prey]!")
+ prey.death()
+ prey = null
+ COOLDOWN_START(src, emote_cooldown, 1 MINUTES)
+ return
+ else // We're hunting an object, and should delete it instead of killing it. Mostly useful for decal bugs like ants or spider webs.
+ manual_emote("chomps [hunted]!")
+ qdel(hunted)
+ hunted = null
+ COOLDOWN_START(src, emote_cooldown, 1 MINUTES)
+ return
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 6db590f9b48..702d587fe03 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -2715,3 +2715,46 @@
var/color
CONVERT_PH_TO_COLOR(exposed_atom.reagents.ph, color)
exposed_atom.add_atom_colour(color, WASHABLE_COLOUR_PRIORITY)
+
+/datum/reagent/ants
+ name = "Ants"
+ description = "A sample of a lost breed of Space Ants (formicidae bastardium tyrannus), they are well-known for ravaging the living shit out of pretty much anything."
+ reagent_state = SOLID
+ color = "#993333"
+ taste_mult = 1.3
+ taste_description = "tiny legs scuttling down the back of your throat."
+ metabolization_rate = 5 * REAGENTS_METABOLISM //1u per second
+ glass_name = "glass of ants"
+ glass_desc = "Bottoms up...?"
+ ph = 4.6 // Ants contain Formic Acid
+ /// How much damage the ants are going to be doing (rises with each tick the ants are in someone's body)
+ var/ant_damage = 0
+ /// Tells the debuff how many ants we are being covered with.
+ var/amount_left = 0
+
+/datum/reagent/ants/on_mob_life(mob/living/carbon/victim, delta_time)
+ victim.adjustBruteLoss(max(0.1, round((ant_damage * 0.005),0.1))) //Scales with time. Around 12.5 brute for 50 seconds.
+ if(DT_PROB(5, delta_time))
+ if(DT_PROB(5, delta_time)) //Super rare statement
+ victim.say("AUGH NO NOT THE ANTS! NOT THE ANTS! AAAAUUGH THEY'RE IN MY EYES! MY EYES! AUUGH!!", forced = /datum/reagent/ants)
+ else
+ victim.say(pick("THEY'RE UNDER MY SKIN!!", "GET THEM OUT OF ME!!", "HOLY HELL THEY BURN!!", "MY GOD THEY'RE INSIDE ME!!", "GET THEM OUT!!"), forced = /datum/reagent/ants)
+ if(DT_PROB(15, delta_time))
+ victim.emote("scream")
+ if(DT_PROB(2, delta_time))
+ victim.vomit(rand(1, 2), stun = FALSE)
+ ant_damage += 1
+ return ..()
+
+/datum/reagent/ants/on_mob_end_metabolize(mob/living/living_anthill)
+ ant_damage = 0
+ to_chat(living_anthill, "You feel like the last of the ants are out of your system.")
+ return ..()
+
+/datum/reagent/ants/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)
+ . = ..()
+ if(!iscarbon(exposed_mob) || (methods & (INGEST|INJECT)))
+ return
+ if(methods & (PATCH|TOUCH|VAPOR))
+ amount_left = round(reac_volume,0.1)
+ exposed_mob.apply_status_effect(STATUS_EFFECT_ANTS, amount_left)
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index 1745c4466eb..dfbf3b5fd07 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -908,3 +908,19 @@
return
clear_products(holder, step_volume_added)
holder.my_atom.audible_message(span_notice("[icon2html(holder.my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] The reaction gives out a fizz, teleporting items everywhere!"))
+
+/datum/chemical_reaction/ants
+ results = list(/datum/reagent/ants = 3)
+ required_reagents = list(/datum/reagent/ants = 2, /datum/reagent/consumable/sugar = 6)
+
+/datum/chemical_reaction/ant_slurry // We're basically glueing ants together with synthflesh & maint sludge to make a bigger ant.
+ required_reagents = list(/datum/reagent/ants = 40, /datum/reagent/medicine/c2/synthflesh = 20, /datum/reagent/drug/maint/sludge = 5)
+ required_temp = 480
+ reaction_flags = REACTION_INSTANT
+ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE
+
+/datum/chemical_reaction/ant_slurry/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume)
+ var/location = get_turf(holder.my_atom)
+ for(var/i in rand(1, created_volume) to created_volume)
+ new /mob/living/simple_animal/ant(location)
+ ..()
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index 3ba3f98b93c..f06b5df4d69 100755
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/icons/mob/pets.dmi b/icons/mob/pets.dmi
index 9a67290f16b..0e457262dcc 100644
Binary files a/icons/mob/pets.dmi and b/icons/mob/pets.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index e89b5dfbfe7..6e2e9471544 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/food/pizzaspaghetti.dmi b/icons/obj/food/pizzaspaghetti.dmi
index 39fda74c97d..7a8525b9cce 100644
Binary files a/icons/obj/food/pizzaspaghetti.dmi and b/icons/obj/food/pizzaspaghetti.dmi differ
diff --git a/icons/obj/objects.dmi b/icons/obj/objects.dmi
index f5c23b32875..2d7121a9b8f 100644
Binary files a/icons/obj/objects.dmi and b/icons/obj/objects.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 0c231d138d1..959766207e9 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -641,6 +641,7 @@
#include "code\datums\components\fantasy\affix.dm"
#include "code\datums\components\fantasy\prefixes.dm"
#include "code\datums\components\fantasy\suffixes.dm"
+#include "code\datums\components\food\decomposition.dm"
#include "code\datums\components\food\edible.dm"
#include "code\datums\components\food\ice_cream_holder.dm"
#include "code\datums\components\plumbing\_plumbing.dm"
@@ -2743,6 +2744,7 @@
#include "code\modules\mob\living\simple_animal\bot\secbot.dm"
#include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm"
#include "code\modules\mob\living\simple_animal\bot\vibebot.dm"
+#include "code\modules\mob\living\simple_animal\friendly\ant.dm"
#include "code\modules\mob\living\simple_animal\friendly\butterfly.dm"
#include "code\modules\mob\living\simple_animal\friendly\cat.dm"
#include "code\modules\mob\living\simple_animal\friendly\crab.dm"