diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 20679a95e6..a4ee7cc3cc 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -123,6 +123,7 @@
#define COMSIG_TURF_MULTIZ_NEW "turf_multiz_new" //from base of turf/New(): (turf/source, direction)
// /atom/movable signals
+#define COMSIG_MOVABLE_PRE_MOVE "movable_pre_move" //from base of atom/movable/Moved(): (/atom)
#define COMSIG_MOVABLE_MOVED "movable_moved" //from base of atom/movable/Moved(): (/atom, dir)
#define COMSIG_MOVABLE_CROSS "movable_cross" //from base of atom/movable/Cross(): (/atom/movable)
#define COMSIG_MOVABLE_CROSSED "movable_crossed" //from base of atom/movable/Crossed(): (/atom/movable)
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 033636017d..d707f7e58a 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -234,6 +234,7 @@
#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
#define NUKEOP_ANTAGONIST "nukeop-antagonist"
#define MADE_UNCLONEABLE "made-uncloneable"
+#define TIMESTOP_TRAIT "timestop"
#define NUKEOP_TRAIT "nuke-op"
#define MEGAFAUNA_TRAIT "megafauna"
#define DEATHSQUAD_TRAIT "deathsquad"
diff --git a/code/datums/components/dejavu.dm b/code/datums/components/dejavu.dm
new file mode 100644
index 0000000000..1bad589c97
--- /dev/null
+++ b/code/datums/components/dejavu.dm
@@ -0,0 +1,107 @@
+/**
+ * A component to reset the parent to its previous state after some time passes
+ */
+/datum/component/dejavu
+ /// The turf the parent was on when this components was applied, they get moved back here after the duration
+ var/turf/starting_turf
+ /// Determined by the type of the parent so different behaviours can happen per type
+ var/rewind_type
+ /// How many rewinds will happen before the effect ends
+ var/rewinds_remaining
+ /// How long to wait between each rewind
+ var/rewind_interval
+
+ /// The starting value of clone loss at the beginning of the effect
+ var/clone_loss = 0
+ /// The starting value of toxin loss at the beginning of the effect
+ var/tox_loss = 0
+ /// The starting value of oxygen loss at the beginning of the effect
+ var/oxy_loss = 0
+ /// The starting value of brain loss at the beginning of the effect
+ var/brain_loss = 0
+ /// The starting value of brute loss at the beginning of the effect
+ /// This only applies to simple animals
+ var/brute_loss
+ /// The starting value of integrity at the beginning of the effect
+ /// This only applies to objects
+ var/integrity
+ /// A list of body parts saved at the beginning of the effect
+ var/list/datum/saved_bodypart/saved_bodyparts
+
+/datum/component/dejavu/Initialize(rewinds = 1, interval = 10 SECONDS)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ starting_turf = get_turf(parent)
+ rewinds_remaining = rewinds
+ rewind_interval = interval
+
+ if(isliving(parent))
+ var/mob/living/L = parent
+ clone_loss = L.getCloneLoss()
+ tox_loss = L.getToxLoss()
+ oxy_loss = L.getOxyLoss()
+ brain_loss = L.getOrganLoss(ORGAN_SLOT_BRAIN)
+ rewind_type = .proc/rewind_living
+
+ if(iscarbon(parent))
+ var/mob/living/carbon/C = parent
+ saved_bodyparts = C.save_bodyparts()
+ rewind_type = .proc/rewind_carbon
+
+ else if(isanimal(parent))
+ var/mob/living/simple_animal/M = parent
+ brute_loss = M.bruteloss
+ rewind_type = .proc/rewind_animal
+
+ else if(isobj(parent))
+ var/obj/O = parent
+ integrity = O.obj_integrity
+ rewind_type = .proc/rewind_obj
+
+ addtimer(CALLBACK(src, rewind_type), rewind_interval)
+
+/datum/component/dejavu/Destroy()
+ starting_turf = null
+ saved_bodyparts = null
+ return ..()
+
+/datum/component/dejavu/proc/rewind()
+ to_chat(parent, "You remember a time not so long ago...")
+
+ //comes after healing so new limbs comically drop to the floor
+ if(starting_turf)
+ var/atom/movable/master = parent
+ master.forceMove(starting_turf)
+
+ rewinds_remaining --
+ if(rewinds_remaining)
+ addtimer(CALLBACK(src, rewind_type), rewind_interval)
+ else
+ to_chat(parent, "But the memory falls out of your reach.")
+ qdel(src)
+
+/datum/component/dejavu/proc/rewind_living()
+ var/mob/living/master = parent
+ master.setCloneLoss(clone_loss)
+ master.setToxLoss(tox_loss)
+ master.setOxyLoss(oxy_loss)
+ master.setOrganLoss(ORGAN_SLOT_BRAIN, brain_loss)
+ rewind()
+
+/datum/component/dejavu/proc/rewind_carbon()
+ if(saved_bodyparts)
+ var/mob/living/carbon/master = parent
+ master.apply_saved_bodyparts(saved_bodyparts)
+ rewind_living()
+
+/datum/component/dejavu/proc/rewind_animal()
+ var/mob/living/simple_animal/master = parent
+ master.bruteloss = brute_loss
+ master.updatehealth()
+ rewind_living()
+
+/datum/component/dejavu/proc/rewind_obj()
+ var/obj/master = parent
+ master.obj_integrity = integrity
+ rewind()
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 829b66a060..42ccc4bbba 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -163,7 +163,7 @@
/atom/movable/proc/Move_Pulled(atom/A)
if(!pulling)
return
- if(pulling.anchored || !pulling.Adjacent(src))
+ if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src))
stop_pulling()
return
if(isliving(pulling))
@@ -190,7 +190,7 @@
log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.")
stop_pulling()
return
- if(pulling.anchored)
+ if(pulling.anchored || pulling.move_resist > move_force)
stop_pulling()
return
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index f556a75434..1dfd5c4143 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -49,6 +49,7 @@
var/lights = FALSE
var/lights_power = 6
var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference
+ var/completely_disabled = FALSE //stops the mech from doing anything
var/breach_time = 0
var/recharge_rate = 0
@@ -431,6 +432,8 @@
return
if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled
return
+ if(completely_disabled)
+ return
if(phasing)
occupant_message("Unable to interact with objects while phasing")
return
@@ -508,6 +511,8 @@
return 1
/obj/mecha/relaymove(mob/user,direction)
+ if(completely_disabled)
+ return
if(!direction)
return
if(user != occupant) //While not "realistic", this piece is player friendly.
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
index bd4f1a8acd..70cb29e7a3 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook.dm
@@ -163,7 +163,7 @@
/datum/spellbook_entry/timestop
name = "Time Stop"
- spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/timestop
+ spell_type = /obj/effect/proc_holder/spell/aoe_turf/timestop
category = "Defensive"
/datum/spellbook_entry/smoke
diff --git a/code/modules/fields/timestop.dm b/code/modules/fields/timestop.dm
index c59a8004ca..56abef8cd6 100644
--- a/code/modules/fields/timestop.dm
+++ b/code/modules/fields/timestop.dm
@@ -9,7 +9,7 @@
pixel_x = -64
pixel_y = -64
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- var/list/immune = list() // the one who creates the timestop is immune
+ var/list/immune = list() // the one who creates the timestop is immune, which includes wizards and the dead slime you murdered to make this chronofield
var/turf/target
var/freezerange = 2
var/duration = 140
@@ -27,10 +27,10 @@
for(var/A in immune_atoms)
immune[A] = TRUE
for(var/mob/living/L in GLOB.player_list)
- if(locate(/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects
+ if(locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects
immune[L] = TRUE
for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.parasites)
- if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop) in G.summoner.mind.spell_list) //It would only make sense that a person's stand would also be immune.
+ if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.mind.spell_list) //It would only make sense that a person's stand would also be immune.
immune[G] = TRUE
if(start)
timestop()
@@ -46,9 +46,8 @@
chronofield = make_field(/datum/proximity_monitor/advanced/timestop, list("current_range" = freezerange, "host" = src, "immune" = immune, "check_anti_magic" = check_anti_magic, "check_holy" = check_holy))
QDEL_IN(src, duration)
-/obj/effect/timestop/wizard
+/obj/effect/timestop/magic
check_anti_magic = TRUE
- duration = 100
/datum/proximity_monitor/advanced/timestop
name = "chronofield"
@@ -56,9 +55,10 @@
field_shape = FIELD_SHAPE_RADIUS_SQUARE
requires_processing = TRUE
var/list/immune = list()
- var/list/mob/living/frozen_mobs = list()
- var/list/obj/item/projectile/frozen_projectiles = list()
- var/list/atom/movable/frozen_throws = list()
+ var/list/frozen_things = list()
+ var/list/frozen_mobs = list() //cached separately for processing
+ var/list/frozen_structures = list() //Also machinery, and only frozen aestethically
+ var/list/frozen_turfs = list() //Only aesthetically
var/check_anti_magic = FALSE
var/check_holy = FALSE
@@ -74,85 +74,125 @@
/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A)
if(immune[A] || global_frozen_atoms[A] || !istype(A))
return FALSE
- if(A.throwing)
- freeze_throwing(A)
+ if(ismob(A))
+ var/mob/M = A
+ if(M.anti_magic_check(check_anti_magic, check_holy))
+ immune[A] = TRUE
+ return
+ var/frozen = TRUE
if(isliving(A))
freeze_mob(A)
else if(istype(A, /obj/item/projectile))
freeze_projectile(A)
+ else if(istype(A, /obj/mecha))
+ freeze_mecha(A)
+ else if((ismachinery(A) && !istype(A, /obj/machinery/light)) || isstructure(A)) //Special exception for light fixtures since recoloring causes them to change light
+ freeze_structure(A)
else
- return FALSE
+ frozen = FALSE
+ if(A.throwing)
+ freeze_throwing(A)
+ frozen = TRUE
+ if(!frozen)
+ return
+ frozen_things[A] = A.move_resist
+ A.move_resist = INFINITY
+ global_frozen_atoms[A] = src
into_the_negative_zone(A)
+ RegisterSignal(A, COMSIG_MOVABLE_PRE_MOVE, .proc/unfreeze_atom)
+ RegisterSignal(A, COMSIG_ITEM_PICKUP, .proc/unfreeze_atom)
return TRUE
/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all()
- for(var/i in frozen_projectiles)
- unfreeze_projectile(i)
- for(var/i in frozen_mobs)
- unfreeze_mob(i)
- for(var/i in frozen_throws)
- unfreeze_throw(i)
+ for(var/i in frozen_things)
+ unfreeze_atom(i)
+ for(var/T in frozen_turfs)
+ unfreeze_turf(T)
+
+/datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A)
+ if(A.throwing)
+ unfreeze_throwing(A)
+ if(isliving(A))
+ unfreeze_mob(A)
+ else if(istype(A, /obj/item/projectile))
+ unfreeze_projectile(A)
+ else if(istype(A, /obj/mecha))
+ unfreeze_mecha(A)
+
+ UnregisterSignal(A, COMSIG_MOVABLE_PRE_MOVE)
+ UnregisterSignal(A, COMSIG_ITEM_PICKUP)
+ escape_the_negative_zone(A)
+ A.move_resist = frozen_things[A]
+ frozen_things -= A
+ global_frozen_atoms -= A
+
+/datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M)
+ M.completely_disabled = TRUE
+
+/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M)
+ M.completely_disabled = FALSE
/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM)
var/datum/thrownthing/T = AM.throwing
T.paused = TRUE
- frozen_throws[AM] = T
- global_frozen_atoms[AM] = TRUE
-/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throw(atom/movable/AM)
- var/datum/thrownthing/T = frozen_throws[AM]
- T.paused = FALSE
- frozen_throws -= AM
- global_frozen_atoms -= AM
+/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throwing(atom/movable/AM)
+ var/datum/thrownthing/T = AM.throwing
+ if(T)
+ T.paused = FALSE
+
+/datum/proximity_monitor/advanced/timestop/proc/freeze_turf(turf/T)
+ into_the_negative_zone(T)
+ frozen_turfs += T
+
+/datum/proximity_monitor/advanced/timestop/proc/unfreeze_turf(turf/T)
+ escape_the_negative_zone(T)
+
+/datum/proximity_monitor/advanced/timestop/proc/freeze_structure(obj/O)
+ into_the_negative_zone(O)
+ frozen_structures += O
+
+/datum/proximity_monitor/advanced/timestop/proc/unfreeze_structure(obj/O)
+ escape_the_negative_zone(O)
/datum/proximity_monitor/advanced/timestop/process()
for(var/i in frozen_mobs)
var/mob/living/m = i
- if(get_dist(get_turf(m), get_turf(host)) > current_range)
- unfreeze_mob(m)
- else
- m.Stun(20, 1, 1)
+ m.Stun(20, 1, 1)
/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T)
for(var/i in T.contents)
freeze_atom(i)
+ freeze_turf(T)
return ..()
-/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/item/projectile/P)
- escape_the_negative_zone(P)
- frozen_projectiles -= P
- P.paused = FALSE
- global_frozen_atoms -= P
-
/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/item/projectile/P)
- frozen_projectiles[P] = TRUE
P.paused = TRUE
- global_frozen_atoms[P] = TRUE
+
+/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/item/projectile/P)
+ P.paused = FALSE
/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L)
- if(L.anti_magic_check(check_anti_magic, check_holy))
- immune += L
- return
+ frozen_mobs += L
L.Stun(20, 1, 1)
- frozen_mobs[L] = L.anchored
- L.anchored = TRUE
- global_frozen_atoms[L] = TRUE
- if(ishostile(L))
- var/mob/living/simple_animal/hostile/H = L
- H.toggle_ai(AI_OFF)
- H.LoseTarget()
+ ADD_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT)
+ walk(L, 0) //stops them mid pathing even if they're stunimmune
+ if(isanimal(L))
+ var/mob/living/simple_animal/S = L
+ S.toggle_ai(AI_OFF)
+ if(ishostile(L))
+ var/mob/living/simple_animal/hostile/H = L
+ H.LoseTarget()
/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L)
- escape_the_negative_zone(L)
L.AdjustStun(-20, 1, 1)
- L.anchored = frozen_mobs[L]
+ REMOVE_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT)
frozen_mobs -= L
- global_frozen_atoms -= L
- if(ishostile(L))
- var/mob/living/simple_animal/hostile/H = L
- H.toggle_ai(initial(H.AIStatus))
+ if(isanimal(L))
+ var/mob/living/simple_animal/S = L
+ S.toggle_ai(initial(S.AIStatus))
//you don't look quite right, is something the matter?
/datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A)
@@ -160,4 +200,4 @@
//let's put some colour back into your cheeks
/datum/proximity_monitor/advanced/timestop/proc/escape_the_negative_zone(atom/A)
- A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
\ No newline at end of file
+ A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 72b7b580f7..a2689f0b5f 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -90,8 +90,10 @@
if(now_pushing)
return TRUE
+ var/they_can_move = TRUE
if(isliving(M))
var/mob/living/L = M
+ they_can_move = L.canmove //L.mobility_flags & MOBILITY_MOVE
//Also spread diseases
for(var/thing in diseases)
var/datum/disease/D = thing
@@ -144,13 +146,17 @@
return 1
if(!M.buckled && !M.has_buckled_mobs())
- var/mob_swap
- //the puller can always swap with its victim if on grab intent
- if(M.pulledby == src && a_intent == INTENT_GRAB)
- mob_swap = 1
- //restrained people act if they were on 'help' intent to prevent a person being pulled from being separated from their puller
- else if((M.restrained() || M.a_intent == INTENT_HELP) && (restrained() || a_intent == INTENT_HELP))
- mob_swap = 1
+ var/mob_swap = FALSE
+ var/too_strong = (M.move_resist > move_force) //can't swap with immovable objects unless they help us
+ if(!they_can_move) //we have to physically move them
+ if(!too_strong)
+ mob_swap = TRUE
+ else
+ if(M.pulledby == src && a_intent == INTENT_GRAB)
+ mob_swap = TRUE
+ //restrained people act if they were on 'help' intent to prevent a person being pulled from being separated from their puller
+ else if((M.restrained() || M.a_intent == INTENT_HELP) && (restrained() || a_intent == INTENT_HELP))
+ mob_swap = TRUE
if(mob_swap)
//switch our position with M
if(loc && !loc.Adjacent(M.loc))
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 6082bdf705..8b2ef507d9 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -84,6 +84,7 @@
var/datum/personal_crafting/handcrafting
var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever), AI_Z_OFF (Temporarily off due to nonpresence of players)
+ var/can_have_ai = TRUE //once we have become sentient, we can never go back
var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check.
@@ -425,6 +426,9 @@
canmove = FALSE
else
canmove = value_otherwise
+ if(!canmove) // !(mobility_flags & MOBILITY_MOVE)
+ walk(src, 0) //stop mid walk
+
update_transform()
update_action_buttons_icon()
return canmove
@@ -443,6 +447,7 @@
/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion
toggle_ai(AI_OFF) // To prevent any weirdness.
+ can_have_ai = FALSE
/mob/living/simple_animal/update_sight()
if(!client)
@@ -557,6 +562,8 @@
LoadComponent(/datum/component/riding)
/mob/living/simple_animal/proc/toggle_ai(togglestatus)
+ if(!can_have_ai && (togglestatus != AI_OFF))
+ return
if (AIStatus != togglestatus)
if (togglestatus > 0 && togglestatus < 5)
if (togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF)
diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm
index 29b4689317..fd6cecf926 100644
--- a/code/modules/mob/living/simple_animal/slime/life.dm
+++ b/code/modules/mob/living/simple_animal/slime/life.dm
@@ -41,7 +41,7 @@
AIproc = 1
while(AIproc && stat != DEAD && (attacked || hungry || rabid || buckled))
- if(buckled) // can't eat AND have this little process at the same time
+ if(!canmove) // !(mobility_flags & MOBILITY_MOVE) //also covers buckling. Not sure why buckled is in the while condition if we're going to immediately break, honestly
break
if(!Target || client)
diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm
index 125219d809..140167c2f0 100644
--- a/code/modules/photography/camera/camera.dm
+++ b/code/modules/photography/camera/camera.dm
@@ -33,6 +33,8 @@
var/picture_size_y_min = 1
var/picture_size_x_max = 4
var/picture_size_y_max = 4
+ var/can_customise = TRUE
+ var/default_picture_name
/obj/item/camera/attack_self(mob/user)
if(!disk)
@@ -199,8 +201,10 @@
user.put_in_hands(p)
pictures_left--
to_chat(user, "[pictures_left] photos left.")
- var/customize = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No")
- if(customize == "Yes")
+ var/customise = "No"
+ if(can_customise)
+ customise = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No")
+ if(customise == "Yes")
var/name1 = stripped_input(user, "Set a name for this photo, or leave blank. 32 characters max.", "Name", max_length = 32)
var/desc1 = stripped_input(user, "Set a description to add to photo, or leave blank. 128 characters max.", "Caption", max_length = 128)
var/caption = stripped_input(user, "Set a caption for this photo, or leave blank. 256 characters max.", "Caption", max_length = 256)
@@ -210,6 +214,10 @@
picture.picture_desc = "[desc1] - [picture.picture_desc]"
if(caption)
picture.caption = caption
+ else
+ if(default_picture_name)
+ picture.picture_name = default_picture_name
+
p.set_picture(picture, TRUE, TRUE)
if(CONFIG_GET(flag/picture_logging_camera))
picture.log_to_file()
\ No newline at end of file
diff --git a/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/code/modules/reagents/chemistry/recipes/slime_extracts.dm
index 2890d02d09..e08fba604d 100644
--- a/code/modules/reagents/chemistry/recipes/slime_extracts.dm
+++ b/code/modules/reagents/chemistry/recipes/slime_extracts.dm
@@ -565,9 +565,16 @@
required_other = TRUE
/datum/chemical_reaction/slime/slimestop/on_reaction(datum/reagents/holder)
+ sleep(50)
+ var/obj/item/slime_extract/sepia/extract = holder.my_atom
var/turf/T = get_turf(holder.my_atom)
- var/list/M = list(get_mob_by_key(holder.my_atom.fingerprintslast))
- new /obj/effect/timestop(T, null, null, M)
+ new /obj/effect/timestop(T, null, null, null)
+ if(istype(extract))
+ if(extract.Uses > 0)
+ var/mob/lastheld = get_mob_by_key(holder.my_atom.fingerprintslast)
+ if(lastheld && !lastheld.equip_to_slot_if_possible(extract, SLOT_HANDS, disable_warning = TRUE))
+ extract.forceMove(get_turf(lastheld))
+
..()
/datum/chemical_reaction/slime/slimecamera
diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm
index e5b30962ae..48e6f720b0 100644
--- a/code/modules/research/xenobiology/crossbreeding/burning.dm
+++ b/code/modules/research/xenobiology/crossbreeding/burning.dm
@@ -154,7 +154,7 @@ Burning extracts:
/obj/item/slimecross/burning/sepia/do_effect(mob/user)
user.visible_message("[src] shapes itself into a camera!")
- new /obj/item/camera/timefreeze(get_turf(user))
+ new /obj/item/camera/rewind(get_turf(user))
..()
/obj/item/slimecross/burning/cerulean
@@ -289,25 +289,88 @@ Burning extracts:
//Misc. things added
+//Rewind camera - I'm already Burning Sepia
+/obj/item/camera/rewind
+ name = "sepia-tinted camera"
+ desc = "They say a picture is like a moment stopped in time."
+ pictures_left = 1
+ pictures_max = 1
+ can_customise = FALSE
+ default_picture_name = "A nostalgic picture"
+ var/used = FALSE
+
+/datum/saved_bodypart
+ var/obj/item/bodypart/old_part
+ var/bodypart_type
+ var/brute_dam
+ var/burn_dam
+ var/stamina_dam
+
+/datum/saved_bodypart/New(obj/item/bodypart/part)
+ old_part = part
+ bodypart_type = part.type
+ brute_dam = part.brute_dam
+ burn_dam = part.burn_dam
+ stamina_dam = part.stamina_dam
+
+/mob/living/carbon/proc/apply_saved_bodyparts(list/datum/saved_bodypart/parts)
+ var/list/dont_chop = list()
+ for(var/zone in parts)
+ var/datum/saved_bodypart/saved_part = parts[zone]
+ var/obj/item/bodypart/already = get_bodypart(zone)
+ if(QDELETED(saved_part.old_part))
+ saved_part.old_part = new saved_part.bodypart_type
+ if(!already || already != saved_part.old_part)
+ saved_part.old_part.replace_limb(src, TRUE)
+ saved_part.old_part.heal_damage(INFINITY, INFINITY, INFINITY, null, FALSE)
+ saved_part.old_part.receive_damage(saved_part.brute_dam, saved_part.burn_dam, saved_part.stamina_dam)
+ dont_chop[zone] = TRUE
+ for(var/_part in bodyparts)
+ var/obj/item/bodypart/part = _part
+ if(dont_chop[part.body_zone])
+ continue
+ part.drop_limb(TRUE)
+
+/mob/living/carbon/proc/save_bodyparts()
+ var/list/datum/saved_bodypart/ret = list()
+ for(var/_part in bodyparts)
+ var/obj/item/bodypart/part = _part
+ var/datum/saved_bodypart/saved_part = new(part)
+
+ ret[part.body_zone] = saved_part
+ return ret
+
+/obj/item/camera/rewind/afterattack(atom/target, mob/user, flag)
+ if(!on || !pictures_left || !isturf(target.loc))
+ return
+ if(!used)//selfie time
+ if(user == target)
+ to_chat(user, "You take a selfie!")
+ else
+ to_chat(user, "You take a photo with [target]!")
+ to_chat(target, "[user] takes a photo with you!")
+ to_chat(target, "You'll remember this moment forever!")
+
+ used = TRUE
+ target.AddComponent(/datum/component/dejavu, 2)
+ .=..()
+
+//Timefreeze camera - Old Burning Sepia result. Kept in case admins want to spawn it
/obj/item/camera/timefreeze
name = "sepia-tinted camera"
desc = "They say a picture is like a moment stopped in time."
pictures_left = 1
pictures_max = 1
+ var/used = FALSE
/obj/item/camera/timefreeze/afterattack(atom/target, mob/user, flag)
if(!on || !pictures_left || !isturf(target.loc))
return
- new /obj/effect/timestop(get_turf(target), 2, 50, list(user))
- . = ..()
- var/text = "The camera fades away"
- if(disk)
- text += ", leaving the disk behind!"
- user.put_in_hands(disk)
- else
- text += "!"
- to_chat(user,"[text]")
- qdel(src)
+ if(!used) //refilling the film does not refill the timestop
+ new /obj/effect/timestop(get_turf(target), 2, 50, list(user))
+ used = TRUE
+ desc = "This camera has seen better days."
+ return ..()
/obj/item/slimepotion/extract_cloner
name = "extract cloning potion"
diff --git a/code/modules/research/xenobiology/crossbreeding/regenerative.dm b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
index 55865d1ae2..d230c22d2f 100644
--- a/code/modules/research/xenobiology/crossbreeding/regenerative.dm
+++ b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
@@ -148,8 +148,9 @@ Regenerative extracts:
/obj/item/slimecross/regenerative/sepia
colour = "sepia"
-/obj/item/slimecross/regenerative/sepia/core_effect(mob/living/target, mob/user)
- new /obj/effect/timestop(get_turf(target), 2, 50, list(user,target))
+/obj/item/slimecross/regenerative/sepia/core_effect_before(mob/living/target, mob/user)
+ to_chat(target, "You try to forget how you feel.")
+ target.AddComponent(/datum/component/dejavu)
/obj/item/slimecross/regenerative/cerulean
colour = "cerulean"
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index a2d7612681..e098fe389d 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -152,19 +152,21 @@
sound1 = 'sound/magic/teleport_diss.ogg'
sound2 = 'sound/magic/teleport_app.ogg'
-/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop
+/obj/effect/proc_holder/spell/aoe_turf/timestop
name = "Stop Time"
desc = "This spell stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen."
charge_max = 500
clothes_req = 1
- invocation = "TOKI WO TOMARE"
+ invocation = "TOKI YO TOMARE"
invocation_type = "shout"
range = 0
cooldown_min = 100
- summon_amt = 1
action_icon_state = "time"
+ var/timestop_range = 2
+ var/timestop_duration = 100
- summon_type = list(/obj/effect/timestop/wizard)
+/obj/effect/proc_holder/spell/aoe_turf/timestop/cast(list/targets, mob/user = usr)
+ new /obj/effect/timestop/magic(get_turf(user), timestop_range, timestop_duration, list(user))
/obj/effect/proc_holder/spell/aoe_turf/conjure/carp
name = "Summon Carp"
diff --git a/tgstation.dme b/tgstation.dme
index 719275bcef..7525168e09 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -362,6 +362,7 @@
#include "code\datums\components\chasm.dm"
#include "code\datums\components\construction.dm"
#include "code\datums\components\decal.dm"
+#include "code\datums\components\dejavu.dm"
#include "code\datums\components\earprotection.dm"
#include "code\datums\components\edit_complainer.dm"
#include "code\datums\components\empprotection.dm"