diff --git a/_maps/shuttles/emergency_narnar.dmm b/_maps/shuttles/emergency_narnar.dmm
index 84973d621d3..3295aee638a 100644
--- a/_maps/shuttles/emergency_narnar.dmm
+++ b/_maps/shuttles/emergency_narnar.dmm
@@ -181,6 +181,10 @@
/area/shuttle/escape)
"I" = (
/obj/effect/rune/narsie,
+/obj/effect/fun_balloon/sentience/emergency_shuttle{
+ effect_range = 5;
+ group_name = "horrible monsters on Shuttle 667"
+ },
/turf/open/floor/plasteel/cult,
/area/shuttle/escape)
"J" = (
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index 12c96a5643a..5b93b43d669 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -17,7 +17,7 @@
/obj/effect/overlay/beam/New()
..()
- spawn(10) qdel(src)
+ QDEL_IN(src, 10)
/obj/effect/overlay/temp
icon_state = "nothing"
@@ -35,8 +35,7 @@
if(randomdir)
setDir(pick(cardinal))
flick("[icon_state]", src) //Because we might be pulling it from a pool, flick whatever icon it uses so it starts at the start of the icon's animation.
- spawn(duration)
- qdel(src)
+ QDEL_IN(src, duration)
/obj/effect/overlay/temp/bloodsplatter
icon = 'icons/effects/blood.dmi'
@@ -273,8 +272,8 @@
duration = 15
/obj/effect/overlay/temp/gib_animation/New(loc, gib_icon)
- icon_state = gib_icon
..()
+ icon_state = gib_icon
/obj/effect/overlay/temp/gib_animation/ex_act(severity)
return //so the overlay isn't deleted by the explosion that gibbed the mob.
@@ -287,8 +286,28 @@
duration = 15
/obj/effect/overlay/temp/dust_animation/New(loc, dust_icon)
- icon_state = dust_icon
..()
+ icon_state = dust_icon
+
+/obj/effect/overlay/temp/sparkle
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "shieldsparkles"
+ mouse_opacity = 0
+ density = 0
+ duration = 10
+ var/atom/movable/attached_to
+
+/obj/effect/overlay/temp/sparkle/New(atom/movable/AM)
+ ..()
+ attached_to = AM
+ attached_to.overlays += src
+
+/obj/effect/overlay/temp/sparkle/Destroy()
+ attached_to.overlays -= src
+ . = ..()
+
+/obj/effect/overlay/temp/sparkle/tailsweep
+ icon_state = "tailsweep"
/obj/effect/overlay/palmtree_r
name = "Palm tree"
@@ -309,4 +328,4 @@
/obj/effect/overlay/coconut
name = "Coconuts"
icon = 'icons/misc/beach.dmi'
- icon_state = "coconuts"
\ No newline at end of file
+ icon_state = "coconuts"
diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm
new file mode 100644
index 00000000000..64d0a762731
--- /dev/null
+++ b/code/modules/admin/fun_balloon.dm
@@ -0,0 +1,85 @@
+/obj/effect/fun_balloon
+ name = "fun balloon"
+ desc = "This is going to be a laugh riot."
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "syndballoon"
+ anchored = TRUE
+ var/popped = FALSE
+
+/obj/effect/fun_balloon/New()
+ . = ..()
+ SSobj.processing |= src
+
+/obj/effect/fun_balloon/Destroy()
+ SSobj.processing -= src
+ . = ..()
+
+/obj/effect/fun_balloon/process()
+ if(!popped && check() && !qdeleted(src))
+ popped = TRUE
+ effect()
+ pop()
+
+/obj/effect/fun_balloon/proc/check()
+ return FALSE
+
+/obj/effect/fun_balloon/proc/effect()
+ return
+
+/obj/effect/fun_balloon/proc/pop()
+ visible_message("[src] pops!")
+ playsound(get_turf(src), 'sound/items/party_horn.ogg', 50, 1, -1)
+ qdel(src)
+
+/obj/effect/fun_balloon/attack_ghost(mob/user)
+ if(!user.client || !user.client.holder || popped)
+ return
+ switch(alert("Pop [src]?","Fun Balloon","Yes","No"))
+ if("Yes")
+ effect()
+ pop()
+
+/obj/effect/fun_balloon/sentience
+ name = "sentience fun balloon"
+ desc = "When this pops, things are gonna get more aware around here."
+ var/effect_range = 3
+ var/group_name = "a bunch of giant spiders"
+
+/obj/effect/fun_balloon/sentience/effect()
+ var/list/bodies = list()
+ for(var/mob/living/M in range(effect_range, get_turf(src)))
+ bodies += M
+
+ var/question = "Would you like to be [group_name]?"
+ var/list/candidates = pollCandidates(question, "pAI", null, FALSE, 100)
+ while(candidates.len && bodies.len)
+ var/mob/dead/observer/ghost = pick_n_take(candidates)
+ var/mob/living/body = pick_n_take(bodies)
+
+ body << "Your mob has been taken over by a ghost!"
+ message_admins("[key_name_admin(ghost)] has taken control \
+ of ([key_name_admin(body)])")
+ body.ghostize(0)
+ body.key = ghost.key
+ PoolOrNew(/obj/effect/overlay/temp/sparkle, body)
+
+/obj/effect/fun_balloon/sentience/emergency_shuttle
+ name = "shuttle sentience fun balloon"
+ var/trigger_time = 60
+
+/obj/effect/fun_balloon/sentience/emergency_shuttle/check()
+ . = FALSE
+ if(SSshuttle.emergency && (SSshuttle.emergency.timeLeft() <= trigger_time) && (SSshuttle.emergency.mode == SHUTTLE_CALL))
+ . = TRUE
+
+/obj/effect/fun_balloon/scatter
+ name = "scatter fun balloon"
+ desc = "When this pops, you're not going to be around here anymore."
+ var/effect_range = 5
+
+/obj/effect/fun_balloon/scatter/effect()
+ for(var/mob/living/M in range(effect_range, get_turf(src)))
+ var/turf/T = find_safe_turf()
+ PoolOrNew(/obj/effect/overlay/temp/sparkle, M)
+ M.forceMove(T)
+ M << "Pop!"
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index ef5c7d5ad19..979c3c60f94 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -298,7 +298,7 @@
selection_type = "view"
sound = 'sound/magic/Repulse.ogg'
var/maxthrow = 5
- var/animation = "shieldsparkles"
+ var/sparkle_path = /obj/effect/overlay/sparkle
action_icon_state = "repulse"
@@ -314,12 +314,10 @@
for(var/atom/movable/AM in thrownatoms)
if(AM == user || AM.anchored) continue
- var/obj/effect/overlay/targeteffect = new /obj/effect/overlay{icon='icons/effects/effects.dmi'; icon_state="shieldsparkles"; mouse_opacity=0; density = 0}()
- targeteffect.icon_state = animation
- AM.add_overlay(targeteffect)
+ // created sparkles will disappear on their own
+ PoolOrNew(sparkle_path, AM)
throwtarget = get_edge_target_turf(user, get_dir(user, get_step_away(AM, user)))
distfromcaster = get_dist(user, AM)
- addtimer(src, "remove_sparkles", 10, FALSE, AM, targeteffect)
if(distfromcaster == 0)
if(istype(AM, /mob/living))
var/mob/living/M = AM
@@ -333,10 +331,6 @@
M << "You're thrown back by [user]!"
AM.throw_at_fast(throwtarget, ((Clamp((maxthrow - (Clamp(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1,user)//So stuff gets tossed around at the same time.
-/obj/effect/proc_holder/spell/aoe_turf/repulse/proc/remove_sparkles(atom/movable/AM, obj/effect/overlay/targeteffect)
- AM.overlays -= targeteffect
- qdel(targeteffect)
-
/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno //i fixed conflicts only to find out that this is in the WIZARD file instead of the xeno file?!
name = "Tail Sweep"
desc = "Throw back attackers with a sweep of your tail."
@@ -346,7 +340,7 @@
range = 2
cooldown_min = 150
invocation_type = "none"
- animation = "tailsweep"
+ sparkle_path = /obj/effect/overlay/sparkle/tailsweep
action_icon_state = "tailsweep"
action_background_icon_state = "bg_alien"
diff --git a/sound/items/party_horn.ogg b/sound/items/party_horn.ogg
new file mode 100644
index 00000000000..ffdd49b1e72
Binary files /dev/null and b/sound/items/party_horn.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 32d0712ff07..8aa2f8dae4e 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -837,6 +837,7 @@
#include "code\modules\admin\create_object.dm"
#include "code\modules\admin\create_poll.dm"
#include "code\modules\admin\create_turf.dm"
+#include "code\modules\admin\fun_balloon.dm"
#include "code\modules\admin\holder2.dm"
#include "code\modules\admin\ipintel.dm"
#include "code\modules\admin\IsBanned.dm"