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"