diff --git a/code/modules/mining/lavaland/loot/ashdragon_loot.dm b/code/modules/mining/lavaland/loot/ashdragon_loot.dm
index 56cc0fbd32a..5f3cc8f298d 100644
--- a/code/modules/mining/lavaland/loot/ashdragon_loot.dm
+++ b/code/modules/mining/lavaland/loot/ashdragon_loot.dm
@@ -39,19 +39,25 @@
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "rended")
flags_2 = RANDOM_BLOCKER_2
var/summon_cooldown = 0
- var/list/mob/dead/observer/spirits
+ /// List of wisps we have active, for cleanup purposes in case a ghost gets randomly deleted.
+ var/list/obj/effect/wisp/ghost/orbs
+ /// List of ghosts currently orbiting us.
+ var/list/mob/dead/observer/ghosts
/obj/item/melee/ghost_sword/New()
..()
- spirits = list()
+ ghosts = list()
+ orbs = list()
register_signals(src)
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
GLOB.poi_list |= src
/obj/item/melee/ghost_sword/Destroy()
- for(var/mob/dead/observer/G in spirits)
+ for(var/mob/dead/observer/G in ghosts)
remove_ghost(G)
- spirits.Cut()
+ // if there are any orbs left (possibly detached from ghosts) ensure they don't stick around
+ for(var/spirit as anything in orbs)
+ qdel(spirit)
remove_signals(src)
UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
GLOB.poi_list -= src
@@ -59,8 +65,8 @@
/obj/item/melee/ghost_sword/examine()
. = ..()
- if(length(spirits))
- . += "It appears to pulse with the power of [length(spirits)] vengeful spirits!"
+ if(length(orbs))
+ . += "It appears to pulse with the power of [length(orbs)] vengeful spirit\s!"
else
. += "It glows weakly."
@@ -83,7 +89,7 @@
/obj/item/melee/ghost_sword/proc/add_ghost(atom/movable/orbited, atom/orbiter)
SIGNAL_HANDLER // COMSIG_ATOM_ORBIT_BEGIN
var/mob/dead/observer/ghost = orbiter
- if(!istype(ghost) || !isobserver(orbiter) || (ghost in spirits))
+ if(!istype(ghost) || !isobserver(orbiter) || (ghost in ghosts))
return
if(!ismob(loc))
@@ -91,23 +97,34 @@
// they'll get added to spirits (and turned visible) when the sword enters a mob's hand then
return
- register_signals(ghost) // Pull in any ghosts that may be orbiting other ghosts TODO THIS MIGHT BE THE FUCKIN PROBLEM
+ register_signals(ghost) // Pull in any ghosts that may be orbiting other ghosts
- spirits |= ghost
- ghost.invisibility = 0
+
+ var/obj/effect/wisp/ghost/orb = new(src)
+ orb.color = ghost.get_runechat_color()
+ orb.alpha = 128
+ orb.orbit(src, clockwise = FALSE)
+ ghosts[ghost] = orb
+ orbs.Add(orb)
+
+ // if a ghost gets deleted, the orb cleans itself up
+ // which then passes the torch to us to clean ourselves up
+ RegisterSignal(orb, COMSIG_PARENT_QDELETING, PROC_REF(on_orb_qdel))
/obj/item/melee/ghost_sword/proc/remove_ghost(atom/movable/orbited, atom/orbiter)
SIGNAL_HANDLER // COMSIG_ATOM_ORBIT_STOP
var/mob/dead/observer/ghost = orbiter
- if(!istype(ghost) || !isobserver(ghost) || !(ghost in spirits))
+ if(!istype(ghost) || !(ghost in ghosts))
return
remove_signals(ghost)
- spirits -= ghost
- ghost.invisibility = initial(ghost.invisibility)
+ var/obj/effect/wisp/ghost/attached_orb = ghosts[ghost]
+ attached_orb.stop_orbit()
+ qdel(attached_orb)
+ ghosts -= ghost
/obj/item/melee/ghost_sword/proc/remove_signals(atom/A)
UnregisterSignal(A, COMSIG_ATOM_ORBIT_STOP)
@@ -126,7 +143,7 @@
if(ismob(old_loc))
remove_signals(old_loc)
- for(var/mob/dead/observer/orbiter in spirits)
+ for(var/mob/dead/observer/orbiter in ghosts)
remove_ghost(src, orbiter)
if(ismob(loc))
@@ -135,20 +152,48 @@
for(var/mob/dead/observer/orbiter in get_orbiters_up_hierarchy(recursive = TRUE))
add_ghost(src, orbiter)
+// clean up wisps
+/obj/item/melee/ghost_sword/proc/on_orb_qdel(obj/effect/wisp/ghost/orb)
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ orbs -= orb
+ for(var/ghost in ghosts)
+ if(ghosts[ghost] == orb)
+ ghosts -= ghost
+ break
+
+
/obj/item/melee/ghost_sword/attack(mob/living/target, mob/living/carbon/human/user)
force = 0
- var/ghost_counter = length(spirits)
+ var/ghost_counter = length(orbs)
force = clamp((ghost_counter * 4), 0, 75)
- user.visible_message("[user] strikes with the force of [ghost_counter] vengeful spirits!")
+ user.visible_message("[user] strikes with the force of [ghost_counter] vengeful spirit\s!")
..()
/obj/item/melee/ghost_sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- var/ghost_counter = length(spirits)
+ var/ghost_counter = length(orbs)
final_block_chance += clamp((ghost_counter * 5), 0, 75)
- owner.visible_message("[owner] is protected by a ring of [ghost_counter] ghosts!")
+ owner.visible_message("[owner] is protected by a ring of [ghost_counter] ghost\s!")
return ..()
+
+/obj/effect/wisp/ghost
+ name = "mischievous wisp"
+ desc = "A wisp that seems to want to get up to shenanigans. It often seems disappointed, for some reason."
+ light_range = 0
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/obj/effect/wisp/ghost/Initialize(mapload, mob/dead/observer/ghost)
+ . = ..()
+ RegisterSignal(ghost, COMSIG_PARENT_QDELETING, PROC_REF(on_ghost_qdel))
+
+/obj/effect/wisp/ghost/proc/on_ghost_qdel(mob/dead/observer/ghost)
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ stop_orbit()
+ // we only live as long as our attached ghost
+ qdel(src)
+
+
// Blood
/obj/item/dragons_blood
diff --git a/code/modules/mob/dead/observer/observer_base.dm b/code/modules/mob/dead/observer/observer_base.dm
index e832baa543e..f766af39a43 100644
--- a/code/modules/mob/dead/observer/observer_base.dm
+++ b/code/modules/mob/dead/observer/observer_base.dm
@@ -35,6 +35,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
///toggle for ghost gas analyzer
var/gas_analyzer = FALSE
var/datum/orbit_menu/orbit_menu
+ /// The "color" their runechat would have had
+ var/alive_runechat_color = "#FFFFFF"
/mob/dead/observer/New(mob/body=null, flags=1)
set_invisibility(GLOB.observer_default_invisibility)
@@ -59,6 +61,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
if(ismob(body))
T = get_turf(body) //Where is the body located?
attack_log_old = body.attack_log_old //preserve our attack logs by copying them to our ghost
+ alive_runechat_color = body.get_runechat_color()
var/mutable_appearance/MA = copy_appearance(body)
if(body.mind && body.mind.name)
@@ -846,3 +849,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return allow_roundstart_observers
return FALSE
+
+/mob/dead/observer/get_runechat_color()
+ return alive_runechat_color