Skip to content

Commit

Permalink
Adds Ants, Decomposition, and some extras. (tgstation#59634)
Browse files Browse the repository at this point in the history
Co-authored-by: Kylerace <[email protected]>
  • Loading branch information
Wallemations and Kylerace authored Jul 16, 2021
1 parent 5dc6964 commit a78799f
Show file tree
Hide file tree
Showing 26 changed files with 524 additions and 25 deletions.
2 changes: 2 additions & 0 deletions code/__DEFINES/dcs/flags.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions code/__DEFINES/status_effects.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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 //
/////////////
Expand Down
147 changes: 147 additions & 0 deletions code/datums/components/food/decomposition.dm
Original file line number Diff line number Diff line change
@@ -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("<span class='notice'>[decomp] gets overtaken by mold and ants! Gross!</span>")
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
16 changes: 13 additions & 3 deletions code/datums/elements/caltrop.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -90,7 +94,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)
. = ..()
Expand Down
87 changes: 86 additions & 1 deletion code/datums/status_effects/debuffs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<span class='warning'>SUBJECTPRONOUN is covered in ants!</span>"
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, "<span class='userdanger'>You're covered in ants!</span>")
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, "<span class='userdanger'>You're covered in MORE ants!</span>")
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, "<span class='notice'>All of the ants are off of your body!</span>")
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, "<span class='danger'>You scratch at the ants on your scalp!.</span>")
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, "<span class='danger'>You scratch at the ants on your arms!</span>")
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, "<span class='danger'>You scratch at the ants on your leg!</span>")
leg.receive_damage(0.1,0)
if(50) // 2% chance
to_chat(victim, "<span class='danger'>You rub some ants away from your eyes!</span>")
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 = "<span class='warning'>JESUS FUCKING CHRIST! CLICK TO GET THOSE THINGS OFF!</span>"
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, "<span class='notice'>You start to shake the ants off!</span>")
if(!do_after(living, 2 SECONDS, target = living))
return
for (var/datum/status_effect/ants/ant_covered in living.status_effects)
to_chat(living, "<span class='notice'>You manage to get some of the ants off!</span>")
ant_covered.ants_remaining -= 10 // 5 Times more ants removed per second than just waiting in place

/datum/status_effect/ghoul
id = "ghoul"
Expand All @@ -980,4 +1065,4 @@
name = "Flesh Servant"
desc = "You are a Ghoul! A eldritch monster reanimated to serve its master."
icon_state = "mind_control"

8 changes: 4 additions & 4 deletions code/datums/status_effects/status_effect.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions code/game/objects/effects/decals/cleanable/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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')
3 changes: 2 additions & 1 deletion code/game/objects/effects/spawners/decals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
8 changes: 8 additions & 0 deletions code/game/objects/items/food/_food.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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()
. = ..()
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Loading

0 comments on commit a78799f

Please sign in to comment.