Fixes runtime in golem body swap, Fixes some hard deletes assocaited with golems and mind masters (#73373)

## About The Pull Request

Fixes #73368 

Golems didn't mind transfer on body swap until after the body was
finished creating, this created an issue as golems also did mind
assignment business while creating the body. So the
`mind.enslave_to_creator()` part runtime error'd and caused the create
to fail, which in turn caused the shell to not be consumed.

While sorting this out, I noticed that shells and golems hold a hard
reference to their owner. Yep, hard deletes. Changes mind `enslaved_to`
to a weakref, changes golem `owner` to a weakref, straight up removes
golem `owner` tracking on species because it was ONLY used for card
board golems when it could've just grabbed mind master

## Why It's Good For The Game

No more infinite golem shells

## Changelog

🆑 Melbert
fix: Transferring golem shells no longer make you a free man and also
results in infinite golem shells
fix: Servant golems are considerably less free
fix: Fixes some hard deletes related to mob minds being enslaved to
other mobs
/🆑
This commit is contained in:
MrMelbert
2023-02-17 16:06:49 -06:00
committed by GitHub
parent 75e5a0a5cb
commit e8d209c6f4
8 changed files with 87 additions and 65 deletions
@@ -14,11 +14,16 @@
prompt_name = "a free golem"
you_are_text = "You are a Free Golem. Your family worships The Liberator."
flavour_text = "In his infinite and divine wisdom, he set your clan free to travel the stars with a single declaration: \"Yeah go do whatever.\""
var/has_owner = FALSE
var/can_transfer = TRUE //if golems can switch bodies to this new shell
var/mob/living/owner = null //golem's owner if it has one
spawner_job_path = /datum/job/free_golem
/// If TRUE, other golems can touch us to swap into this shell.
var/can_transfer = TRUE
/// Weakref to the creator of this golem shell.
var/datum/weakref/owner_ref
/obj/effect/mob_spawn/ghost_role/human/golem/Initialize(mapload, datum/species/golem/species = null, mob/creator = null)
/obj/effect/mob_spawn/ghost_role/human/golem/Initialize(mapload, datum/species/golem/species, mob/creator)
if(creator)
name = "inert servant golem shell"
prompt_name = "servant golem"
if(species) //spawners list uses object name to register so this goes before ..()
name += " ([initial(species.prefix)])"
mob_species = species
@@ -26,44 +31,47 @@
var/area/init_area = get_area(src)
if(!mapload && init_area)
notify_ghosts("\A [initial(species.prefix)] golem shell has been completed in \the [init_area.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_key = POLL_IGNORE_GOLEM)
if(has_owner && creator)
if(creator)
you_are_text = "You are a golem."
flavour_text = "You move slowly, but are highly resistant to heat and cold as well as blunt trauma. You are unable to wear clothes, but can still use most tools."
important_text = "Serve [creator], and assist [creator.p_them()] in completing [creator.p_their()] goals at any cost."
owner = creator
owner_ref = WEAKREF(creator)
spawner_job_path = /datum/job/servant_golem
/obj/effect/mob_spawn/ghost_role/human/golem/name_mob(mob/living/spawned_mob, forced_name)
if(!forced_name)
var/datum/species/golem/golem_species = mob_species
if(has_owner)
if(owner_ref?.resolve())
forced_name = "[initial(golem_species.prefix)] Golem ([rand(1,999)])"
else
golem_species = new mob_species
forced_name = golem_species.random_name()
. = ..()
return ..()
/obj/effect/mob_spawn/ghost_role/human/golem/special(mob/living/new_spawn, mob/mob_possessor)
. = ..()
var/datum/species/golem/X = mob_species
to_chat(new_spawn, "[initial(X.info_text)]")
if(!owner)
to_chat(new_spawn, "Build golem shells in the autolathe, and feed refined mineral sheets to the shells to bring them to life! You are generally a peaceful group unless provoked.")
try_keep_home(new_spawn)
var/mob/living/real_owner = owner_ref?.resolve()
var/datum/species/golem/golem_species = mob_species
to_chat(new_spawn, "[initial(golem_species.info_text)]")
if(isnull(real_owner))
if(!is_station_level(new_spawn.z))
to_chat(new_spawn, "Build golem shells in the autolathe, and feed refined mineral sheets to the shells to bring them to life! \
You are generally a peaceful group unless provoked.")
try_keep_home(new_spawn)
else if(new_spawn.mind)
new_spawn.mind.enslave_mind_to_creator(real_owner)
else
new_spawn.mind.enslave_mind_to_creator(owner)
new_spawn.log_message("possessed a golem shell enslaved to [key_name(owner)].", LOG_GAME)
log_admin("[key_name(new_spawn)] possessed a golem shell enslaved to [key_name(owner)].")
stack_trace("[type] created a golem without a mind.")
new_spawn.log_message("possessed a golem shell[real_owner ? " enslaved to [key_name(real_owner)]" : ""].", LOG_GAME)
log_admin("[key_name(new_spawn)] possessed a golem shell[real_owner ? " enslaved to [key_name(real_owner)]" : ""].")
if(ishuman(new_spawn))
var/mob/living/carbon/human/H = new_spawn
if(has_owner)
var/datum/species/golem/G = H.dna.species
G.owner = owner
H.set_cloned_appearance()
if(has_owner && new_spawn.mind)
new_spawn.mind.set_assigned_role_with_greeting(SSjob.GetJobType(/datum/job/servant_golem))
else
new_spawn.mind.set_assigned_role_with_greeting(SSjob.GetJobType(/datum/job/free_golem))
var/mob/living/carbon/human/human_spawn = new_spawn
human_spawn.set_cloned_appearance()
/obj/effect/mob_spawn/ghost_role/human/golem/proc/try_keep_home(mob/new_spawn)
var/static/list/allowed_areas = typecacheof(list(/area/icemoon, /area/lavaland, /area/ruin)) + typecacheof(/area/misc/survivalpod)
@@ -75,28 +83,32 @@
. = ..()
if(.)
return
if(isgolem(user) && can_transfer)
var/mob/living/carbon/human/H = user
var/transfer_choice = tgui_alert(usr, "Transfer your soul to [src]? (Warning, your old body will die!)",,list("Yes","No"))
if(transfer_choice != "Yes")
return
if(QDELETED(src) || uses <= 0)
return
H.log_message("golem-swapped into [src].", LOG_GAME)
H.visible_message(span_notice("A faint light leaves [H], moving to [src] and animating it!"),span_notice("You leave your old body behind, and transfer into [src]!"))
show_flavor = FALSE
var/mob/living/carbon/human/newgolem = create(newname = H.real_name)
H.transfer_quirk_datums(newgolem)
H.mind.transfer_to(newgolem)
H.death()
if(!isgolem(user) || !can_transfer)
return
var/mob/living/carbon/human/golem = user
var/transfer_choice = tgui_alert(usr, "Transfer your soul to [src]? (Warning, your old body will die!)",,list("Yes","No"))
if(transfer_choice != "Yes")
return
if(QDELETED(src) || uses <= 0)
return
uses -= 1
golem.log_message("golem-swapped into [src].", LOG_GAME)
golem.visible_message(
span_notice("A faint light leaves [golem], moving to [src] and animating it!"),
span_notice("You leave your old body behind, and transfer into [src]!"),
)
show_flavor = FALSE
var/mob/living/carbon/human/newgolem = create(user, golem.real_name)
golem.transfer_quirk_datums(newgolem)
golem.death()
check_uses()
return TRUE
/obj/effect/mob_spawn/ghost_role/human/golem/servant
has_owner = TRUE
name = "inert servant golem shell"
prompt_name = "servant golem"
/obj/effect/mob_spawn/ghost_role/human/golem/adamantine
name = "dust-caked free golem shell"
desc = "A humanoid shape, empty, lifeless, and full of potential."
+10 -6
View File
@@ -15,7 +15,7 @@
////Human specific stuff. Don't set these if you aren't using a human, the unit tests will put a stop to your sinful hand.
///sets the human as a species, use a typepath (example: /datum/species/skeleton)
var/mob_species
var/datum/species/mob_species
///equips the human with an outfit.
var/datum/outfit/outfit
///for mappers to override parts of the outfit. really only in here for secret away missions, please try to refrain from using this out of laziness
@@ -181,7 +181,15 @@
/obj/effect/mob_spawn/ghost_role/special(mob/living/spawned_mob, mob/mob_possessor)
. = ..()
if(mob_possessor)
spawned_mob.ckey = mob_possessor.ckey
if(mob_possessor.mind)
mob_possessor.mind.transfer_to(spawned_mob, force_key_move = TRUE)
else
spawned_mob.key = mob_possessor.key
var/datum/mind/spawned_mind = spawned_mob.mind
if(spawned_mind)
spawned_mob.mind.set_assigned_role_with_greeting(SSjob.GetJobType(spawner_job_path))
spawned_mind.name = spawned_mob.real_name
if(show_flavor)
var/output_message = "<span class='infoplain'><span class='big bold'>[you_are_text]</span></span>"
if(flavour_text != "")
@@ -189,10 +197,6 @@
if(important_text != "")
output_message += "\n[span_userdanger("[important_text]")]"
to_chat(spawned_mob, output_message)
var/datum/mind/spawned_mind = spawned_mob.mind
if(spawned_mind)
spawned_mob.mind.set_assigned_role(SSjob.GetJobType(spawner_job_path))
spawned_mind.name = spawned_mob.real_name
/// Checks if the spawner has zero uses left, if so, delete yourself... NOW!
/obj/effect/mob_spawn/ghost_role/proc/check_uses()