From e8d209c6f4fe4f6bfc2e8bb92a9aab512eec1de5 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:06:49 -0600 Subject: [PATCH] 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 :cl: 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 /:cl: --- code/datums/mind/_mind.dm | 5 +- code/datums/mind/antag.dm | 2 +- code/modules/antagonists/cult/cult.dm | 3 +- .../sentient_creature/sentient_creature.dm | 16 ++-- .../mapfluff/ruins/lavaland_ruin_code.dm | 4 +- .../carbon/human/species_types/golems.dm | 12 ++- .../mob_spawn/ghost_roles/golem_roles.dm | 94 +++++++++++-------- code/modules/mob_spawn/mob_spawn.dm | 16 ++-- 8 files changed, 87 insertions(+), 65 deletions(-) diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm index 597a3c038b5..efcd0aed2c9 100644 --- a/code/datums/mind/_mind.dm +++ b/code/datums/mind/_mind.dm @@ -62,8 +62,9 @@ var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud = null var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this. - ///If this mind's master is another mob (i.e. adamantine golems) - var/mob/living/enslaved_to + ///If this mind's master is another mob (i.e. adamantine golems). Weakref of a /living. + var/datum/weakref/enslaved_to + var/datum/language_holder/language_holder var/unconvertable = FALSE var/late_joiner = FALSE diff --git a/code/datums/mind/antag.dm b/code/datums/mind/antag.dm index a00e903aa5b..2061f934d5d 100644 --- a/code/datums/mind/antag.dm +++ b/code/datums/mind/antag.dm @@ -211,7 +211,7 @@ add_antag_datum(N,converter.nuke_team) - enslaved_to = creator + enslaved_to = WEAKREF(creator) current.faction |= creator.faction creator.faction |= current.faction diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 9b50a259805..6ae27dabda3 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -489,7 +489,8 @@ return FALSE if(specific_cult?.is_sacrifice_target(target.mind)) return FALSE - if(target.mind.enslaved_to && !IS_CULTIST(target.mind.enslaved_to)) + var/mob/living/master = target.mind.enslaved_to?.resolve() + if(master && !IS_CULTIST(master)) return FALSE if(target.mind.unconvertable) return FALSE diff --git a/code/modules/antagonists/sentient_creature/sentient_creature.dm b/code/modules/antagonists/sentient_creature/sentient_creature.dm index 5b2d98e0697..fd168360d65 100644 --- a/code/modules/antagonists/sentient_creature/sentient_creature.dm +++ b/code/modules/antagonists/sentient_creature/sentient_creature.dm @@ -20,18 +20,18 @@ return final_icon /datum/antagonist/sentient_creature/on_gain() - if(owner.enslaved_to) - owner.current.copy_languages(owner.enslaved_to, LANGUAGE_MASTER) + var/mob/living/master = owner.enslaved_to?.resolve() + if(master) + owner.current.copy_languages(master, LANGUAGE_MASTER) owner.current.update_atom_languages() . = ..() /datum/antagonist/sentient_creature/ui_static_data(mob/user) var/list/data = list() - if(owner.enslaved_to) - data["enslaved_to"] = owner.enslaved_to.real_name - data["p_them"] = owner.enslaved_to.p_them() - data["p_their"] = owner.enslaved_to.p_their() + var/mob/living/master = owner.enslaved_to?.resolve() + if(master) + data["enslaved_to"] = master.real_name + data["p_them"] = master.p_them() + data["p_their"] = master.p_their() data["holographic"] = owner.current.flags_1 & HOLOGRAM_1 return data - - diff --git a/code/modules/mapfluff/ruins/lavaland_ruin_code.dm b/code/modules/mapfluff/ruins/lavaland_ruin_code.dm index 0157d20bfec..be1e9ea9011 100644 --- a/code/modules/mapfluff/ruins/lavaland_ruin_code.dm +++ b/code/modules/mapfluff/ruins/lavaland_ruin_code.dm @@ -38,10 +38,10 @@ icon = 'icons/obj/wizard.dmi' icon_state = "construct" desc = "The incomplete body of a golem. Add ten sheets of any mineral to finish." - var/shell_type = /obj/effect/mob_spawn/ghost_role/human/golem - var/has_owner = FALSE //if the resulting golem obeys someone w_class = WEIGHT_CLASS_BULKY + var/shell_type = /obj/effect/mob_spawn/ghost_role/human/golem + /obj/item/golem_shell/attackby(obj/item/I, mob/user, params) . = ..() var/static/list/golem_shell_species_types = list( diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index e665555b60d..addd3f87f29 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -54,7 +54,6 @@ var/list/special_names = list("Tarkus") var/human_surname_chance = 3 var/special_name_chance = 5 - var/owner //dobby is a free golem /datum/species/golem/random_name(gender,unique,lastname) var/golem_surname = pick(GLOB.golem_names) @@ -1086,10 +1085,15 @@ to_chat(H, span_warning("You do not have enough cardboard!")) return FALSE to_chat(H, span_notice("You create a new cardboard golem shell.")) - create_brother(H.loc) + create_brother(H, H.loc) + +/datum/species/golem/cardboard/proc/create_brother(mob/living/carbon/human/golem, atom/location) + var/mob/living/master = golem.mind.enslaved_to?.resolve() + if(master) + new /obj/effect/mob_spawn/ghost_role/human/golem/servant(location, /datum/species/golem/cardboard, master) + else + new /obj/effect/mob_spawn/ghost_role/human/golem(location, /datum/species/golem/cardboard) -/datum/species/golem/cardboard/proc/create_brother(location) - new /obj/effect/mob_spawn/ghost_role/human/golem/servant(location, /datum/species/golem/cardboard, owner) last_creation = world.time /datum/species/golem/leather diff --git a/code/modules/mob_spawn/ghost_roles/golem_roles.dm b/code/modules/mob_spawn/ghost_roles/golem_roles.dm index 7d3ee0f92a0..7df4e530a75 100644 --- a/code/modules/mob_spawn/ghost_roles/golem_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/golem_roles.dm @@ -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." diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index 6d4818bec1c..cc0a2590fb1 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -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 = "[you_are_text]" 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()