diff --git a/code/datums/soullink.dm b/code/datums/soullink.dm
new file mode 100644
index 00000000000..1f17c635e84
--- /dev/null
+++ b/code/datums/soullink.dm
@@ -0,0 +1,214 @@
+/mob/living
+ var/list/ownedSoullinks //soullinks we are the owner of
+ var/list/sharedSoullinks //soullinks we are a/the sharer of
+
+/mob/living/Destroy()
+ for(var/s in ownedSoullinks)
+ var/datum/soullink/S = s
+ S.ownerDies(FALSE)
+ qdel(s) //If the owner is destroy()'d, the soullink is destroy()'d
+ ownedSoullinks = null
+ for(var/s in sharedSoullinks)
+ var/datum/soullink/S = s
+ S.sharerDies(FALSE)
+ S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed
+ sharedSoullinks = null
+ return ..()
+
+//Keeps track of a Mob->Mob (potentially Player->Player) connection
+//Can be used to trigger actions on one party when events happen to another
+//Eg: shared deaths
+//Can be used to form a linked list of mob-hopping
+//Does NOT transfer with minds
+/datum/soullink
+ var/mob/living/soulowner
+ var/mob/living/soulsharer
+ var/id //Optional ID, for tagging and finding specific instances
+
+/datum/soullink/Destroy()
+ if(soulowner)
+ LAZYREMOVE(soulowner.ownedSoullinks, src)
+ soulowner = null
+ if(soulsharer)
+ LAZYREMOVE(soulsharer.sharedSoullinks, src)
+ soulsharer = null
+ return ..()
+
+/datum/soullink/proc/removeSoulsharer(mob/living/sharer)
+ if(soulsharer == sharer)
+ soulsharer = null
+ LAZYREMOVE(sharer.sharedSoullinks, src)
+
+//Used to assign variables, called primarily by soullink()
+//Override this to create more unique soullinks (Eg: 1->Many relationships)
+//Return TRUE/FALSE to return the soullink/null in soullink()
+/datum/soullink/proc/parseArgs(mob/living/owner, mob/living/sharer)
+ if(!owner || !sharer)
+ return FALSE
+ soulowner = owner
+ soulsharer = sharer
+ LAZYADD(owner.ownedSoullinks, src)
+ LAZYADD(sharer.sharedSoullinks, src)
+ return TRUE
+
+//Runs after /living death()
+//Override this for content
+/datum/soullink/proc/ownerDies(gibbed, mob/living/owner)
+
+//Runs after /living death()
+//Override this for content
+/datum/soullink/proc/sharerDies(gibbed, mob/living/owner)
+
+//Runs after /living update_revive()
+//Override this for content
+/datum/soullink/proc/ownerRevives(mob/living/owner)
+
+//Runs after /living update_revive()
+//Override this for content
+/datum/soullink/proc/sharerRevives(mob/living/owner)
+
+//Quick-use helper
+/proc/soullink(typepath, ...)
+ var/datum/soullink/S = new typepath()
+ if(S.parseArgs(arglist(args.Copy(2, 0))))
+ return S
+
+
+
+/////////////////
+// MULTISHARER //
+/////////////////
+//Abstract soullink for use with 1 Owner -> Many Sharer setups
+/datum/soullink/multisharer
+ var/list/soulsharers
+
+/datum/soullink/multisharer/parseArgs(mob/living/owner, list/sharers)
+ if(!owner || !LAZYLEN(sharers))
+ return FALSE
+ soulowner = owner
+ soulsharers = sharers
+ LAZYADD(owner.ownedSoullinks, src)
+ for(var/l in sharers)
+ var/mob/living/L = l
+ LAZYADD(L.sharedSoullinks, src)
+ return TRUE
+
+/datum/soullink/multisharer/removeSoulsharer(mob/living/sharer)
+ LAZYREMOVE(soulsharers, sharer)
+
+
+
+/////////////////
+// SHARED FATE //
+/////////////////
+//When the soulowner dies, the soulsharer dies, and vice versa
+//This is intended for two players(or AI) and two mobs
+
+/datum/soullink/sharedfate/ownerDies(gibbed, mob/living/owner)
+ if(soulsharer)
+ soulsharer.death(gibbed)
+
+/datum/soullink/sharedfate/sharerDies(gibbed, mob/living/sharer)
+ if(soulowner)
+ soulowner.death(gibbed)
+
+/////////////////
+// Demon Bind //
+/////////////////
+//When the soulowner dies, the soulsharer dies, but NOT vice versa
+//This is intended for two players(or AI) and two mobs
+
+/datum/soullink/oneway/ownerDies(gibbed, mob/living/owner)
+ if(soulsharer)
+ soulsharer.dust(FALSE)
+
+
+/////////////////
+// SHARED BODY //
+/////////////////
+//When the soulsharer dies, they're placed in the soulowner, who remains alive
+//If the soulowner dies, the soulsharer is killed and placed into the soulowner (who is still dying)
+//This one is intended for one player moving between many mobs
+
+/datum/soullink/sharedbody/ownerDies(gibbed, mob/living/owner)
+ if(soulowner && soulsharer)
+ if(soulsharer.mind)
+ soulsharer.mind.transfer_to(soulowner)
+ soulsharer.death(gibbed)
+
+/datum/soullink/sharedbody/sharerDies(gibbed, mob/living/sharer)
+ if(soulowner && soulsharer && soulsharer.mind)
+ soulsharer.mind.transfer_to(soulowner)
+
+
+
+//////////////////////
+// REPLACEMENT POOL //
+//////////////////////
+//When the owner dies, one of the sharers is placed in the owner's body, fully healed
+//Sort of a "winner-stays-on" soullink
+//Gibbing ends it immediately
+
+/datum/soullink/multisharer/replacementpool/ownerDies(gibbed, mob/living/owner)
+ if(LAZYLEN(soulsharers) && !gibbed) //let's not put them in some gibs
+ var/list/souls = shuffle(soulsharers.Copy())
+ for(var/l in souls)
+ var/mob/living/L = l
+ if(L.stat != DEAD && L.mind)
+ L.mind.transfer_to(soulowner)
+ soulowner.revive(TRUE, TRUE)
+ L.death(FALSE)
+
+//Lose your claim to the throne!
+/datum/soullink/multisharer/replacementpool/sharerDies(gibbed, mob/living/sharer)
+ removeSoulsharer(sharer)
+
+
+////////////////
+// SOUL HOOK //
+////////////////
+// When the owner transitions from dead to alive or vice versa,
+// the linked atom is notified
+
+// Atoms that actually utilize this system are responsible for handling the GC cleanup themselves
+// Splashing the GC handling onto every atom would be a big old waste
+
+/datum/soullink/soulhook
+ var/atom/movable/otherend
+
+/datum/soullink/soulhook/Destroy()
+ if(otherend)
+ LAZYREMOVE(otherend.sharedSoulhooks, src)
+ otherend = null
+ return ..()
+
+/datum/soullink/soulhook/parseArgs(mob/living/owner, atom/movable/other)
+ if(!owner || !other)
+ return FALSE
+ soulowner = owner
+ otherend = other
+ LAZYADD(owner.ownedSoullinks, src)
+ LAZYADD(other.sharedSoulhooks, src)
+ return TRUE
+
+/datum/soullink/soulhook/ownerDies(gibbed, mob/living/owner)
+ if(otherend)
+ otherend.onSoullinkDeath(gibbed, owner)
+
+/datum/soullink/soulhook/ownerRevives(mob/living/owner)
+ if(otherend)
+ otherend.onSoullinkRevive(owner)
+
+// oops I'm butchering the application of this
+/datum/soullink/soulhook/removeSoulsharer(atom/movable/other)
+ if(otherend == other)
+ otherend = null
+ LAZYREMOVE(other.sharedSoulhooks, src)
+ qdel(src) // not much point in a soul link with one end out of the picture for good
+
+/atom/movable
+ var/list/sharedSoulhooks = null
+
+/atom/movable/proc/onSoullinkDeath(gibbed, mob/living/owner)
+
+/atom/movable/proc/onSoullinkRevive(mob/living/owner)
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 721372b56f5..b85fb249c2e 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -87,6 +87,10 @@
/obj/machinery/clonepod/Destroy()
if(connected)
connected.pods -= src
+ for(var/s in sharedSoulhooks)
+ var/datum/soullink/S = s
+ S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed
+ sharedSoulhooks = null
QDEL_NULL(Radio)
QDEL_NULL(countdown)
QDEL_LIST(missing_organs)
@@ -288,6 +292,8 @@
to_chat(clonemind.current, {"Your body is
beginning to regenerate in a cloning pod. You will
become conscious when it is complete."})
+ // Set up a soul link with the dead body to catch a revival
+ soullink(/datum/soullink/soulhook, clonemind.current, src)
update_icon()
@@ -467,7 +473,12 @@
update_clone_antag(occupant)
to_chat(occupant, "There is a bright flash!
\
You feel like a new being.")
- occupant.flash_eyes(visual = 1)
+ occupant.flash_eyes(visual = 1)
+ for(var/s in sharedSoulhooks)
+ var/datum/soullink/S = s
+ S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed
+ sharedSoulhooks = null
+
for(var/i in missing_organs)
qdel(i)
@@ -479,17 +490,22 @@
occupant = null
update_icon()
-/obj/machinery/clonepod/proc/malfunction()
+/obj/machinery/clonepod/proc/malfunction(go_easy = FALSE)
if(occupant)
connected_message("Critical Error!")
announce_radio_message("Critical error! Please contact a Thinktronic Systems technician, as your warranty may be affected.")
- if(occupant.mind != clonemind)
- clonemind.transfer_to(occupant)
- occupant.grab_ghost() // We really just want to make you suffer.
- to_chat(occupant, {"Agony blazes across your
- consciousness as your body is torn apart.
- Is this what dying is like? Yes it is."})
- occupant << sound('sound/hallucinations/veryfar_noise.ogg',0,1,50)
+ for(var/s in sharedSoulhooks)
+ var/datum/soullink/S = s
+ S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed
+ sharedSoulhooks = null
+ if(!go_easy)
+ if(occupant.mind != clonemind)
+ clonemind.transfer_to(occupant)
+ occupant.grab_ghost() // We really just want to make you suffer.
+ to_chat(occupant, {"Agony blazes across your
+ consciousness as your body is torn apart.
+ Is this what dying is like? Yes it is."})
+ occupant << sound('sound/hallucinations/veryfar_noise.ogg',0,1,50)
for(var/i in missing_organs)
qdel(i)
missing_organs.Cut()
@@ -542,6 +558,11 @@
else
return
+/obj/machinery/clonepod/onSoullinkRevive(mob/living/L)
+ if(occupant && L == clonemind.current)
+ // The old body's back in shape, time to ditch the cloning one
+ malfunction(go_easy = TRUE)
+
/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H)
LAZYINITLIST(missing_organs)
for(var/i in missing_organs)
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 69e86de81e6..206571bec93 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -143,9 +143,9 @@
return ..(gibbed)
/mob/living/carbon/human/update_revive()
- ..()
+ . = ..()
// Update healthdoll
- if(healthdoll)
+ if(. && healthdoll)
// We're alive again, so re-build the entire healthdoll
healthdoll.cached_healthdoll_overlays.Cut()
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index c5ce705ba17..8a043dbf591 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -3,4 +3,12 @@
clear_fullscreens()
update_action_buttons_icon()
+
+ for(var/s in ownedSoullinks)
+ var/datum/soullink/S = s
+ S.ownerDies(gibbed, src)
+ for(var/s in sharedSoullinks)
+ var/datum/soullink/S = s
+ S.sharerDies(gibbed, src)
+
..(gibbed)
diff --git a/code/modules/mob/living/stat_states.dm b/code/modules/mob/living/stat_states.dm
index 58c4221504d..812eeaa0821 100644
--- a/code/modules/mob/living/stat_states.dm
+++ b/code/modules/mob/living/stat_states.dm
@@ -37,9 +37,9 @@
// handles revival through other means than cloning or adminbus (defib, IPC repair)
/mob/living/proc/update_revive(updating = TRUE)
if(stat != DEAD)
- return
+ return 0
if(!can_be_revived())
- return
+ return 0
add_logs(src, null, "came back to life at [atom_loc_line(get_turf(src))]", admin=0, print_attack_log = 0)
stat = CONSCIOUS
dead_mob_list -= src
@@ -49,3 +49,11 @@
update_canmove()
// update_blind_effects()
updatehealth()
+
+ for(var/s in ownedSoullinks)
+ var/datum/soullink/S = s
+ S.ownerRevives(src)
+ for(var/s in sharedSoullinks)
+ var/datum/soullink/S = s
+ S.sharerRevives(src)
+ return 1
diff --git a/paradise.dme b/paradise.dme
index 71edb0e003d..e0bd5450ef0 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -218,6 +218,7 @@
#include "code\datums\recipe.dm"
#include "code\datums\ruins.dm"
#include "code\datums\shuttles.dm"
+#include "code\datums\soullink.dm"
#include "code\datums\spell.dm"
#include "code\datums\statclick.dm"
#include "code\datums\supplypacks.dm"