diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 2275c4b90b..b7750556d5 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -253,6 +253,7 @@
// item traits
#define TRAIT_NODROP "nodrop"
+#define TRAIT_SPOOKY_THROW "spooky_throw"
// common trait sources
#define TRAIT_GENERIC "generic"
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 4e593ba904..f376ba50d7 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -130,7 +130,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
),
/obj/item = list(
"TRAIT_NODROP" = TRAIT_NODROP,
- "TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT
+ "TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT,
+ "TRAIT_SPOOKY_THROW" = TRAIT_SPOOKY_THROW
)
))
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index e5d6b0bbd0..0fa8035d72 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -84,6 +84,8 @@ SUBSYSTEM_DEF(throwing)
/datum/thrownthing/Destroy()
+ if(HAS_TRAIT_FROM(thrownthing, TRAIT_SPOOKY_THROW, "revenant"))
+ REMOVE_TRAIT(thrownthing, TRAIT_SPOOKY_THROW, "revenant")
SSthrowing.processing -= thrownthing
thrownthing.throwing = null
thrownthing = null
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index f93d40bb04..2fa5a20d7a 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -449,6 +449,10 @@
// this must come before the screen objects only block, dunno why it wasn't before
if(over_object == M)
user_show_to_mob(M)
+ return
+ if(isrevenant(M))
+ RevenantThrow(over_object, M, source)
+ return
if(!M.incapacitated())
if(!istype(over_object, /obj/screen))
dump_content_at(over_object, M)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index bbbce9b8fd..a582fa03e4 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -574,8 +574,8 @@
return TRUE
//TODO: Better floating
-/atom/movable/proc/float(on)
- if(throwing)
+/atom/movable/proc/float(on, throw_override)
+ if(throwing || !throw_override)
return
if(on && !(movement_type & FLOATING))
animate(src, pixel_y = 2, time = 10, loop = -1, flags = ANIMATION_RELATIVE)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 8e3441400a..608768a0c7 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -468,6 +468,10 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
melee_attack_chain(usr, over)
usr.FlushCurrentAction()
return TRUE //returning TRUE as a "is this overridden?" flag
+ if(isrevenant(usr))
+ if(RevenantThrow(over, usr, src))
+ return
+
if(!Adjacent(usr) || !over.Adjacent(usr))
return // should stop you from dragging through windows
diff --git a/code/game/objects/items/candle.dm b/code/game/objects/items/candle.dm
index a3366e714e..960e3ce499 100644
--- a/code/game/objects/items/candle.dm
+++ b/code/game/objects/items/candle.dm
@@ -82,4 +82,8 @@
/obj/item/candle/infinite/hugbox
heats_space = FALSE
+/obj/item/candle/DoRevenantThrowEffects(atom/target)
+ if(!infinite)
+ put_out_candle()
+
#undef CANDLE_LUMINOSITY
diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm
index de32375642..4280e7105f 100644
--- a/code/game/objects/items/cigs_lighters.dm
+++ b/code/game/objects/items/cigs_lighters.dm
@@ -136,6 +136,12 @@ CIGARETTE PACKETS ARE IN FANCY.DM
STOP_PROCESSING(SSobj, src)
. = ..()
+/obj/item/clothing/mask/cigarette/DoRevenantThrowEffects(atom/target)
+ if(lit)
+ attackby()
+ else
+ light()
+
/obj/item/clothing/mask/cigarette/attackby(obj/item/W, mob/user, params)
if(!lit && smoketime > 0)
var/lighting_text = W.ignition_effect(src, user)
@@ -517,6 +523,9 @@ CIGARETTE PACKETS ARE IN FANCY.DM
overlay_state = pick(overlay_list)
update_icon()
+/obj/item/lighter/DoRevenantThrowEffects(atom/target)
+ set_lit()
+
/obj/item/lighter/suicide_act(mob/living/carbon/user)
if (lit)
user.visible_message("[user] begins holding \the [src]'s flame up to [user.p_their()] face! It looks like [user.p_theyre()] trying to commit suicide!")
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index a2459bce9f..e29519406a 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -37,11 +37,14 @@
/obj/item/flashlight/attack_self(mob/user)
on = !on
update_brightness(user)
- playsound(user, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, 1)
+ playsound(src, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, TRUE)
for(var/X in actions)
var/datum/action/A = X
A.UpdateButtonIcon()
- return 1
+ return TRUE
+
+/obj/item/flashlight/DoRevenantThrowEffects(atom/target)
+ attack_self()
/obj/item/flashlight/suicide_act(mob/living/carbon/human/user)
if (user.eye_blind)
diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm
index 0d5c9a22aa..39c8143ee9 100644
--- a/code/game/objects/items/eightball.dm
+++ b/code/game/objects/items/eightball.dm
@@ -46,6 +46,9 @@
if(.)
new /obj/item/toy/eightball/haunted(loc)
+/obj/item/toy/eightball/DoRevenantThrowEffects(atom/target)
+ MakeHaunted()
+
/obj/item/toy/eightball/attack_self(mob/user)
if(shaking)
return
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index b1f51f608d..c1579dfe15 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -235,16 +235,23 @@
return
EmptyExtinguisher(user)
-/obj/item/extinguisher/proc/EmptyExtinguisher(var/mob/user)
- if(loc == user && reagents.total_volume)
+/obj/item/extinguisher/DoRevenantThrowEffects(atom/target)
+ EmptyExtinguisher()
+
+/obj/item/extinguisher/proc/EmptyExtinguisher(mob/user)
+ if(!reagents.total_volume)
+ return
+ if(loc == user || !user)
reagents.clear_reagents()
var/turf/T = get_turf(loc)
if(isopenturf(T))
var/turf/open/theturf = T
theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
-
- user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] by using its release valve.")
+ if(user)
+ user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] by using its release valve.")
+ else
+ user.visible_message("The release valve of \the [src] suddenly opens and sprays it's contents on the floor!")
//firebot assembly
/obj/item/extinguisher/attackby(obj/O, mob/user, params)
diff --git a/code/game/objects/items/pinpointer.dm b/code/game/objects/items/pinpointer.dm
index abd9cec950..81bae3d54b 100644
--- a/code/game/objects/items/pinpointer.dm
+++ b/code/game/objects/items/pinpointer.dm
@@ -30,9 +30,13 @@
target = null
return ..()
+/obj/item/pinpointer/DoRevenantThrowEffects(atom/target)
+ attack_self()
+
/obj/item/pinpointer/attack_self(mob/living/user)
active = !active
- user.visible_message("[user] [active ? "" : "de"]activates [user.p_their()] pinpointer.", "You [active ? "" : "de"]activate your pinpointer.")
+ if(user)
+ user.visible_message("[user] [active ? "" : "de"]activates [user.p_their()] pinpointer.", "You [active ? "" : "de"]activate your pinpointer.")
playsound(src, 'sound/items/screwdriver2.ogg', 50, 1)
if(active)
START_PROCESSING(SSfastprocess, src)
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index c5193d8b19..c213fa34df 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -45,6 +45,10 @@
return
set_snowflake_from_config(id)
+/obj/item/toy/plush/DoRevenantThrowEffects(atom/target)
+ var/datum/component/squeak/squeaker = GetComponent(/datum/component/squeak)
+ squeaker.do_play_squeak(TRUE)
+
/obj/item/toy/plush/Initialize(mapload, set_snowflake_id)
. = ..()
AddComponent(/datum/component/squeak, squeak_override)
diff --git a/code/game/objects/items/pneumaticCannon.dm b/code/game/objects/items/pneumaticCannon.dm
index 23be8cbb9a..1db5cdd526 100644
--- a/code/game/objects/items/pneumaticCannon.dm
+++ b/code/game/objects/items/pneumaticCannon.dm
@@ -43,6 +43,13 @@
/obj/item/pneumatic_cannon/proc/init_charge() //wrapper so it can be vv'd easier
START_PROCESSING(SSobj, src)
+/obj/item/pneumatic_cannon/DoRevenantThrowEffects(atom/target)
+ var/picked_target
+ var/list/possible_targets = range(3,src)
+ picked_target = pick(possible_targets)
+ if(target)
+ Fire(null, picked_target)
+
/obj/item/pneumatic_cannon/process()
if(++charge_tick >= charge_ticks && charge_type)
fill_with_type(charge_type, charge_amount)
@@ -134,21 +141,29 @@
Fire(user, target)
/obj/item/pneumatic_cannon/proc/Fire(mob/living/user, var/atom/target)
- if(!istype(user) && !target)
+ if(!target)
return
+ if(user)
+ if(!isliving(user))
+ return
var/discharge = 0
- if(!can_trigger_gun(user))
+ if(user && !can_trigger_gun(user))
return
if(!loadedItems || !loadedWeightClass)
- to_chat(user, "\The [src] has nothing loaded.")
+ if(user)
+ to_chat(user, "\The [src] has nothing loaded.")
return
if(!tank && checktank)
- to_chat(user, "\The [src] can't fire without a source of gas.")
+ if(user)
+ to_chat(user, "\The [src] can't fire without a source of gas.")
return
if(tank && !tank.air_contents.remove(gasPerThrow * pressureSetting))
- to_chat(user, "\The [src] lets out a weak hiss and doesn't react!")
+ if(user)
+ to_chat(user, "\The [src] lets out a weak hiss and doesn't react!")
+ else
+ visible_message(src, "\The [src] lets out a weak hiss and doesn't react!")
return
- if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(75) && clumsyCheck && iscarbon(user))
+ if(user && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(75) && clumsyCheck && iscarbon(user))
var/mob/living/carbon/C = user
C.visible_message("[C] loses [C.p_their()] grip on [src], causing it to go off!", "[src] slips out of your hands and goes off!")
C.dropItemToGround(src, TRUE)
@@ -157,15 +172,18 @@
else
var/list/possible_targets = range(3,src)
target = pick(possible_targets)
- discharge = 1
- if(!discharge)
+ discharge = TRUE
+ if(!discharge && user)
user.visible_message("[user] fires \the [src]!", \
"You fire \the [src]!")
- log_combat(user, target, "fired at", src)
var/turf/T = get_target(target, get_turf(src))
- playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 50, 1)
- fire_items(T, user)
- if(pressureSetting >= 3 && iscarbon(user))
+ playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 50, TRUE)
+ if(user)
+ log_combat(user, target, "fired at", src)
+ fire_items(T, user)
+ else
+ fire_items(T)
+ if(user && pressureSetting >= 3 && iscarbon(user))
var/mob/living/carbon/C = user
C.visible_message("[C] is thrown down by the force of the cannon!", "[src] slams into your shoulder, knocking you down!")
C.DefaultCombatKnockdown(60)
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index b52d138385..1de77bd8b9 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -47,6 +47,9 @@
cell = new preload_cell_type(src)
update_icon()
+/obj/item/melee/baton/DoRevenantThrowEffects(atom/target)
+ switch_status()
+
/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
..()
//Only mob/living types have stun handling
diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm
index 08b91332ef..d57f0cc51f 100644
--- a/code/game/objects/items/tanks/tanks.dm
+++ b/code/game/objects/items/tanks/tanks.dm
@@ -73,6 +73,15 @@
/obj/item/tank/proc/populate_gas()
return
+/obj/item/tank/DoRevenantThrowEffects(atom/target)
+ if(air_contents)
+ var/turf/open/location = get_turf(src)
+ if(istype(location))
+ location.assume_air(air_contents)
+ air_contents.clear()
+ SSair.add_to_active(location)
+ visible_message("[src] can't be turned on while unsecured!")
+ if(user)
+ to_chat(user, "[src] can't be turned on while unsecured!")
return
welding = !welding
if(welding)
if(get_fuel() >= 1)
- to_chat(user, "You switch [src] on.")
+ if(user)
+ to_chat(user, "You switch [src] on.")
playsound(loc, acti_sound, 50, 1)
force = 15
damtype = "fire"
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index f5ebcffe35..ef58e5af39 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -72,6 +72,7 @@
var/list/drained_mobs = list() //Cannot harvest the same mob twice
var/perfectsouls = 0 //How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?)
var/generated_objectives_and_spells = FALSE
+ var/telekinesis_cooldown
/mob/living/simple_animal/revenant/Initialize(mapload)
. = ..()
@@ -93,13 +94,16 @@
/mob/living/simple_animal/revenant/Login()
..()
- to_chat(src, "You are a revenant.")
- to_chat(src, "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.")
- to_chat(src, "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.")
- to_chat(src, "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.")
- to_chat(src, "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.")
- to_chat(src, "You do not remember anything of your past lives, nor will you remember anything about this one after your death.")
- to_chat(src, "Be sure to read the wiki page to learn more.")
+ var/revenant_greet
+ revenant_greet += "You are a revenant."
+ revenant_greet += "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant."
+ revenant_greet += "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds."
+ revenant_greet += "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable."
+ revenant_greet += "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities."
+ revenant_greet += "You do not remember anything of your past lives, nor will you remember anything about this one after your death."
+ revenant_greet += "Be sure to read the wiki page to learn more."
+ revenant_greet += "You are also able to telekinetically throw objects by clickdragging them."
+ to_chat(src, revenant_greet)
if(!generated_objectives_and_spells)
generated_objectives_and_spells = TRUE
mind.assigned_role = ROLE_REVENANT
@@ -317,6 +321,12 @@
to_chat(src, "Lost [essence_amt]E[source ? " from [source]":""].")
return 1
+/mob/living/simple_animal/revenant/proc/telekinesis_cooldown_end()
+ if(!telekinesis_cooldown)
+ CRASH("telekinesis_cooldown_end ran when telekinesis_cooldown on [src] was false")
+ else
+ telekinesis_cooldown = FALSE
+
/mob/living/simple_animal/revenant/proc/death_reset()
revealed = FALSE
unreveal_time = 0
@@ -431,6 +441,38 @@
qdel(revenant)
..()
+/proc/RevenantThrow(over, mob/user, obj/item/throwable)
+ var/mob/living/simple_animal/revenant/spooker = user
+ if(!istype(throwable))
+ return
+ if(!throwable.anchored && !spooker.telekinesis_cooldown && spooker.essence > 20)
+ if(7 < get_dist(throwable, spooker))
+ return
+ if(3 >= get_dist(throwable, spooker))
+ spooker.stun(10)
+ spooker.reveal(25)
+ else
+ spooker.stun(20)
+ spooker.reveal(50)
+ spooker.change_essence_amount(-20, FALSE, "telekinesis")
+ spooker.telekinesis_cooldown = TRUE
+ throwable.float(TRUE, TRUE)
+ sleep(20)
+ throwable.DoRevenantThrowEffects(over)
+ throwable.throw_at(over, 10, 2)
+ ADD_TRAIT(throwable, TRAIT_SPOOKY_THROW, "revenant")
+ log_combat(throwable, over, "spooky telekinesised at", throwable)
+ var/obj/effect/temp_visual/telekinesis/T = new(get_turf(throwable))
+ T.color = "#8715b4"
+ addtimer(CALLBACK(spooker, /mob/living/simple_animal/revenant.proc/telekinesis_cooldown_end), 50)
+ sleep(5)
+ throwable.float(FALSE, TRUE)
+
+
+//Use this for effects you want to happen when a revenant throws stuff, check the TRAIT_SPOOKY_THROW if you want to know if its still being thrown
+/obj/item/proc/DoRevenantThrowEffects(atom/target)
+ return TRUE
+
//objectives
/datum/objective/revenant
var/targetAmount = 100
diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm
index 8a0645f311..07a9f499f8 100644
--- a/code/modules/assembly/flash.dm
+++ b/code/modules/assembly/flash.dm
@@ -30,6 +30,9 @@
attack(user,user)
return FIRELOSS
+/obj/item/assembly/flash/DoRevenantThrowEffects(atom/target)
+ AOE_flash()
+
/obj/item/assembly/flash/update_icon(flash = FALSE)
cut_overlays()
attached_overlays = list()
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 20ec678e45..a00145f9dc 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -193,6 +193,13 @@
user.visible_message(ignition_message)
add_fingerprint(user)
fire_act(I.get_temperature())
+//I would have it become a paper plane before the throw, but that would risk runtimes
+/obj/item/paper/DoRevenantThrowEffects(atom/target)
+ sleep(10)
+ if(HAS_TRAIT(src, TRAIT_SPOOKY_THROW))
+ return
+ new /obj/item/paperplane(get_turf(src))
+ qdel(src)
/obj/item/paper/attackby(obj/item/P, mob/living/user, params)
if(burn_paper_product_attackby_check(P, user))