From 5765f259ef8b304a5f82b5bf2acc51ff9438f11c Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Tue, 25 Nov 2025 20:52:19 -0600 Subject: [PATCH] Fixes human skeleton pirates, Refactors ghost spawns (#94138) ## About The Pull Request `create_from_ghost` can be called from stuff like... dynamic executing a ruleset, so putting a sleep/user input within it causes problems So I refactored ghost spawns a fair bit, creating a clear delineation between where you can and can't put user input ## Changelog :cl: Melbert fix: Pirates will no longer randomly spawn as human fix: Servant Golems have numbered names again refactor: Refactored ghost spawns (like spider eggs or pirate spawners), report any oddities /:cl: --- code/__DEFINES/_flags.dm | 4 - code/__DEFINES/mob_spawn.dm | 9 + code/_globalvars/bitfields.dm | 4 +- .../antagonists/pirate/pirate_roles.dm | 10 +- code/modules/bitrunning/spawners.dm | 8 +- .../virtual_domain/domains/wendigo.dm | 4 +- code/modules/client/preferences.dm | 14 +- .../ruins/icemoonruin_code/mining_site.dm | 2 +- .../mapfluff/ruins/spaceruin_code/meateor.dm | 2 +- code/modules/mob/dead/observer/observer.dm | 8 +- code/modules/mob_spawn/corpses/job_corpses.dm | 6 +- .../mob_spawn/corpses/mining_corpses.dm | 10 +- .../mob_spawn/corpses/nanotrasen_corpses.dm | 2 +- .../mob_spawn/corpses/nonhuman_corpses.dm | 12 +- .../mob_spawn/ghost_roles/away_roles.dm | 4 +- .../ghost_roles/fugitive_hunter_roles.dm | 4 +- .../mob_spawn/ghost_roles/golem_roles.dm | 13 +- .../mob_spawn/ghost_roles/mining_roles.dm | 8 +- .../mob_spawn/ghost_roles/space_roles.dm | 6 +- .../mob_spawn/ghost_roles/spider_roles.dm | 18 +- .../mob_spawn/ghost_roles/unused_roles.dm | 12 +- .../mob_spawn/ghost_roles/venus_human_trap.dm | 3 +- code/modules/mob_spawn/mob_spawn.dm | 166 ++++++++++-------- 23 files changed, 177 insertions(+), 152 deletions(-) diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm index 8b2ea7bf320..53b1514ed6a 100644 --- a/code/__DEFINES/_flags.dm +++ b/code/__DEFINES/_flags.dm @@ -359,7 +359,3 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define EMPATH_SEE_COLD (1<<7) /// Can the empath see if living mob has the fundamentally evil trait #define EMPATH_SEE_EVIL (1<<8) - -// Flags for using your static for a ghost role -#define GHOSTROLE_ALLOW_SPECIES (1<<0) -#define GHOSTROLE_ALLOW_OTHER (1<<1) diff --git a/code/__DEFINES/mob_spawn.dm b/code/__DEFINES/mob_spawn.dm index 6e9fec3bed0..a54ca799307 100644 --- a/code/__DEFINES/mob_spawn.dm +++ b/code/__DEFINES/mob_spawn.dm @@ -2,3 +2,12 @@ #define CORPSE_INSTANT 1 ///this mob spawn creates the corpse during GAME_STATE_PLAYING #define CORPSE_ROUNDSTART 2 + +// Flags for using your static for a ghost role +/// Ghost role will take on the player's species +#define GHOSTROLE_TAKE_PREFS_SPECIES (1<<0) +/// Ghost role will take on the player's apperance (though exlcuding name) +#define GHOSTROLE_TAKE_PREFS_APPEARANCE (1<<1) + +/// Return from create to stop the spawn process. Falsy value so one can just check !create() +#define CANCEL_SPAWN FALSE diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 1abddde7448..400b0609122 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -702,6 +702,6 @@ DEFINE_BITFIELD(visible_info, list( )) DEFINE_BITFIELD(allow_custom_character, list( - "GHOSTROLE_ALLOW_SPECIES" = GHOSTROLE_ALLOW_SPECIES, - "GHOSTROLE_ALLOW_OTHER" = GHOSTROLE_ALLOW_OTHER, + "GHOSTROLE_TAKE_PREFS_SPECIES" = GHOSTROLE_TAKE_PREFS_SPECIES, + "GHOSTROLE_TAKE_PREFS_APPEARANCE" = GHOSTROLE_TAKE_PREFS_APPEARANCE, )) diff --git a/code/modules/antagonists/pirate/pirate_roles.dm b/code/modules/antagonists/pirate/pirate_roles.dm index f2e76c6aee6..d5998c7d29a 100644 --- a/code/modules/antagonists/pirate/pirate_roles.dm +++ b/code/modules/antagonists/pirate/pirate_roles.dm @@ -14,7 +14,7 @@ you_are_text = "You are a space pirate." flavour_text = "The station refused to pay for your protection. Protect the ship, siphon the credits from the station, and raid it for even more loot." spawner_job_path = /datum/job/space_pirate - allow_custom_character = GHOSTROLE_ALLOW_OTHER + allow_custom_character = GHOSTROLE_TAKE_PREFS_APPEARANCE ///Rank of the pirate on the ship, it's used in generating pirate names! var/rank = "Deserter" ///Path of the structure we spawn after creating a pirate. @@ -26,7 +26,7 @@ ///json key to pirate names, the last part ("fish" in "Cometfish") var/name_endings = "generic_endings" -/obj/effect/mob_spawn/ghost_role/human/pirate/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/pirate/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.fully_replace_character_name(spawned_mob.real_name, generate_pirate_name(spawned_mob.gender)) spawned_mob.mind.add_antag_datum(/datum/antagonist/pirate) @@ -36,7 +36,7 @@ var/endings = strings(PIRATE_NAMES_FILE, name_endings) return "[rank ? rank + " " : ""][pick(beggings)][pick(endings)]" -/obj/effect/mob_spawn/ghost_role/human/pirate/create(mob/mob_possessor, newname) +/obj/effect/mob_spawn/ghost_role/human/pirate/create(mob/mob_possessor, newname, apply_prefs) if(fluff_spawn) new fluff_spawn(drop_location()) return ..() @@ -209,7 +209,7 @@ outfit = /datum/outfit/pirate/medieval rank = "Footsoldier" -/obj/effect/mob_spawn/ghost_role/human/pirate/medieval/special(mob/living/carbon/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/pirate/medieval/special(mob/living/carbon/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() if(rank == "Footsoldier") spawned_mob.add_traits(list(TRAIT_NOGUNS, TRAIT_TOSS_GUN_HARD), INNATE_TRAIT) @@ -221,7 +221,7 @@ rank = "Warlord" outfit = /datum/outfit/pirate/medieval/warlord -/obj/effect/mob_spawn/ghost_role/human/pirate/medieval/warlord/special(mob/living/carbon/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/pirate/medieval/warlord/special(mob/living/carbon/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.dna.add_mutation(/datum/mutation/hulk/superhuman, MUTATION_SOURCE_GHOST_ROLE) spawned_mob.dna.add_mutation(/datum/mutation/gigantism, MUTATION_SOURCE_GHOST_ROLE) diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm index fad6da01e59..4baa533c104 100644 --- a/code/modules/bitrunning/spawners.dm +++ b/code/modules/bitrunning/spawners.dm @@ -9,7 +9,7 @@ var/antag = TRUE -/obj/effect/mob_spawn/ghost_role/human/virtual_domain/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) var/datum/mind/ghost_mind = mob_possessor.mind if(ghost_mind) // Preserves any previous bodies before making the switch spawned_mob.AddComponent(/datum/component/temporary_body, ghost_mind, ghost_mind.current, TRUE) @@ -34,7 +34,7 @@ outfit = /datum/outfit/beachbum spawner_job_path = /datum/job/beach_bum antag = FALSE - allow_custom_character = GHOSTROLE_ALLOW_OTHER + allow_custom_character = GHOSTROLE_TAKE_PREFS_APPEARANCE /obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/lifeguard name = "virtual lifeguard sleeper" @@ -43,7 +43,7 @@ outfit = /datum/outfit/beachbum/lifeguard allow_custom_character = NONE -/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/lifeguard/special(mob/living/carbon/human/lifeguard, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/lifeguard/special(mob/living/carbon/human/lifeguard, mob/mob_possessor, apply_prefs) . = ..() lifeguard.gender = FEMALE lifeguard.update_body() @@ -76,7 +76,7 @@ head = /obj/item/clothing/head/costume/pirate/bandana/armored shoes = /obj/item/clothing/shoes/pirate/armored -/obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.fully_replace_character_name(spawned_mob.real_name, "[pick(strings(PIRATE_NAMES_FILE, "generic_beginnings"))][pick(strings(PIRATE_NAMES_FILE, "generic_endings"))]") diff --git a/code/modules/bitrunning/virtual_domain/domains/wendigo.dm b/code/modules/bitrunning/virtual_domain/domains/wendigo.dm index 469234f0819..890d7f6891e 100644 --- a/code/modules/bitrunning/virtual_domain/domains/wendigo.dm +++ b/code/modules/bitrunning/virtual_domain/domains/wendigo.dm @@ -8,10 +8,10 @@ map_name = "wendigo" reward_points = BITRUNNER_REWARD_HIGH -/obj/effect/mob_spawn/corpse/human/bitrunner/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/bitrunner/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.apply_status_effect(/datum/status_effect/gutted) -/obj/effect/mob_spawn/corpse/human/cyber_police/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/cyber_police/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.apply_status_effect(/datum/status_effect/gutted) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index ae43704bd97..85a889193a5 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -503,13 +503,23 @@ GLOBAL_LIST_EMPTY(preferences_datums) apply_character_randomization_prefs(is_antag) apply_prefs_to(character, icon_updates) -/// Applies the given preferences to a human mob. -/datum/preferences/proc/apply_prefs_to(mob/living/carbon/human/character, icon_updates = TRUE) +/** + * Applies the given preferences to a human mob. + * + * Arguments: + * * character - The human mob to apply the preferences to + * * icon_updates - Whether to update the mob's icons after applying preferences. + * Is often skipped to save processing when an update will happen later anyway. + * * do_not_apply - A list of preference types to skip when applying preferences. + */ +/datum/preferences/proc/apply_prefs_to(mob/living/carbon/human/character, icon_updates = TRUE, list/do_not_apply) character.dna.features = list() for (var/datum/preference/preference as anything in get_preferences_in_priority_order()) if (preference.savefile_identifier != PREFERENCE_CHARACTER) continue + if (preference.type in do_not_apply) + continue preference.apply_to_human(character, read_preference(preference.type)) diff --git a/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm b/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm index 7c1e4f7ddf2..94a5af89478 100644 --- a/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm +++ b/code/modules/mapfluff/ruins/icemoonruin_code/mining_site.dm @@ -27,7 +27,7 @@ icon_state = "corpseminer" // Gives the minesite corpses the gutted effect so that the boss ignores them -/obj/effect/mob_spawn/corpse/human/minesite/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/minesite/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.apply_status_effect(/datum/status_effect/gutted) diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm index 542c2bd36f2..145a5ad667a 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm @@ -11,7 +11,7 @@ /// Tiger cultist corpse but with an exit wound /obj/effect/mob_spawn/corpse/human/tigercultist/perforated -/obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() var/obj/item/bodypart/chest/their_chest = spawned_human.get_bodypart(BODY_ZONE_CHEST) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 968a6090b89..4fb1f734741 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -22,9 +22,11 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) light_on = FALSE shift_to_open_context_menu = FALSE var/can_reenter_corpse - var/started_as_observer //This variable is set to 1 when you enter the game as an observer. - //If you died in the game and are a ghost - this will remain as null. - //Note that this is not a reliable way to determine if admins started as observers, since they change mobs a lot. + ///This variable is set to 1 when you enter the game as an observer. + ///If you died in the game and are a ghost - this will remain as FALSE. + ///Note that this is not a reliable way to determine if admins started as observers, since they change mobs a lot. + var/started_as_observer = FALSE + var/atom/movable/following = null ///The time between being able to use boo(), if fun_verbs is TRUE. diff --git a/code/modules/mob_spawn/corpses/job_corpses.dm b/code/modules/mob_spawn/corpses/job_corpses.dm index b0014424037..572bb9f6f8e 100644 --- a/code/modules/mob_spawn/corpses/job_corpses.dm +++ b/code/modules/mob_spawn/corpses/job_corpses.dm @@ -59,15 +59,15 @@ outfit = /datum/outfit/job/assistant icon_state = "corpsegreytider" -/obj/effect/mob_spawn/corpse/human/assistant/beesease_infection/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/assistant/beesease_infection/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.ForceContractDisease(new /datum/disease/beesease) -/obj/effect/mob_spawn/corpse/human/assistant/brainrot_infection/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/assistant/brainrot_infection/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.ForceContractDisease(new /datum/disease/brainrot) -/obj/effect/mob_spawn/corpse/human/assistant/spanishflu_infection/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/assistant/spanishflu_infection/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.ForceContractDisease(new /datum/disease/fluspanish) diff --git a/code/modules/mob_spawn/corpses/mining_corpses.dm b/code/modules/mob_spawn/corpses/mining_corpses.dm index 1b15f0a39c0..da860fdb3b6 100644 --- a/code/modules/mob_spawn/corpses/mining_corpses.dm +++ b/code/modules/mob_spawn/corpses/mining_corpses.dm @@ -8,7 +8,7 @@ burn_damage = 1000 mob_species = /datum/species/skeleton -/obj/effect/mob_spawn/corpse/human/charredskeleton/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/charredskeleton/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() spawned_human.color = "#454545" spawned_human.gender = NEUTER @@ -34,7 +34,7 @@ outfit = select_outfit() return ..() -/obj/effect/mob_spawn/corpse/human/legioninfested/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/legioninfested/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() var/obj/item/organ/legion_tumour/cancer = new() cancer.Insert(spawned_human, special = TRUE, movement_flags = DELETE_IF_REPLACED) @@ -75,7 +75,7 @@ /// Corpse spawner used by dwarf legions to make small corpses /obj/effect/mob_spawn/corpse/human/legioninfested/dwarf -/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/legioninfested/dwarf/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() spawned_human.dna.add_mutation(/datum/mutation/dwarfism, MUTATION_SOURCE_MUTATOR) @@ -123,7 +123,7 @@ /obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/select_outfit() return null -/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() spawned_human.gender = NEUTER @@ -134,7 +134,7 @@ brute_damage = 0 burn_damage = 1000 -/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/legioninfested/skeleton/charred/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() spawned_human.color = "#454545" spawned_human.AddComponent(/datum/component/storm_hating) diff --git a/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm b/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm index c546173f556..92cc3a6b45f 100644 --- a/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm +++ b/code/modules/mob_spawn/corpses/nanotrasen_corpses.dm @@ -54,7 +54,7 @@ outfit = /datum/outfit/centcom/centcom_intern/unarmed mob_name = "Nameless Intern" -/obj/effect/mob_spawn/corpse/human/intern/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/human/intern/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() spawned_human.gender = MALE //we're making it canon babies spawned_human.update_body() diff --git a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm index e25bb36ec32..e5864db94e4 100644 --- a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm +++ b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm @@ -5,13 +5,13 @@ /obj/effect/mob_spawn/corpse/ai mob_type = /mob/living/silicon/ai/spawned -/obj/effect/mob_spawn/corpse/ai/create(mob/user, newname) +/obj/effect/mob_spawn/corpse/ai/create(mob/user, newname, apply_prefs) var/ai_already_present = locate(/mob/living/silicon/ai) in loc if(ai_already_present) - return - . = ..() + return CANCEL_SPAWN + return ..() -/obj/effect/mob_spawn/corpse/ai/special(mob/living/silicon/ai/spawned/dead_ai, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/ai/special(mob/living/silicon/ai/spawned/dead_ai, mob/mob_possessor, apply_prefs) . = ..() dead_ai.name = src.name dead_ai.real_name = src.name @@ -24,7 +24,7 @@ ///the color of the slime you're spawning. var/slime_species = /datum/slime_type/grey -/obj/effect/mob_spawn/corpse/slime/special(mob/living/basic/slime/spawned_slime, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/slime/special(mob/living/basic/slime/spawned_slime, mob/mob_possessor, apply_prefs) . = ..() spawned_slime.set_slime_type(slime_species) @@ -33,7 +33,7 @@ //mostly for unit tests to not get alarmed (which by all means it should because this is a mess) mob_type = /obj/item/clothing/mask/facehugger -/obj/effect/mob_spawn/corpse/facehugger/create(mob/user) +/obj/effect/mob_spawn/corpse/facehugger/create(mob/user, newname, apply_prefs) var/obj/item/clothing/mask/facehugger/spawned_facehugger = new mob_type(loc) spawned_facehugger.Die() qdel(src) diff --git a/code/modules/mob_spawn/ghost_roles/away_roles.dm b/code/modules/mob_spawn/ghost_roles/away_roles.dm index 386e30ea88d..a445f4c4809 100644 --- a/code/modules/mob_spawn/ghost_roles/away_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/away_roles.dm @@ -14,7 +14,7 @@ flavour_text = "Walk this mortal plane and terrorize all living adventurers who dare cross your path." spawner_job_path = /datum/job/skeleton -/obj/effect/mob_spawn/ghost_role/human/skeleton/special(mob/living/new_spawn, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/skeleton/special(mob/living/new_spawn, mob/mob_possessor, apply_prefs) . = ..() to_chat(new_spawn, "You have this horrible lurching feeling deep down that your binding to this world will fail if you abandon this zone... Were you reanimated to protect something?") new_spawn.AddComponent(/datum/component/stationstuck, PUNISHMENT_MURDER, "You experience a feeling like a stressed twine being pulled until it snaps. Then, merciful nothing.") @@ -30,7 +30,7 @@ you_are_text = "By unknown powers, your rotting remains have been resurrected!" flavour_text = "Walk this mortal plane and terrorize all living adventurers who dare cross your path." -/obj/effect/mob_spawn/ghost_role/human/zombie/special(mob/living/new_spawn, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/zombie/special(mob/living/new_spawn, mob/mob_possessor, apply_prefs) . = ..() to_chat(new_spawn, "You have this horrible lurching feeling deep down that your binding to this world will fail if you abandon this zone... Were you reanimated to protect something?") new_spawn.AddComponent(/datum/component/stationstuck, PUNISHMENT_MURDER, "You experience a feeling like a stressed twine being pulled until it snaps. Then, merciful nothing.") diff --git a/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm b/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm index a07d17ec60c..0753a8f9099 100644 --- a/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm @@ -6,9 +6,9 @@ flavour_text = "Write me some god damn flavor text!" //the flavor text will be the backstory argument called on the antagonist's greet, see hunter.dm for details show_flavor = FALSE var/back_story = "error" - allow_custom_character = GHOSTROLE_ALLOW_OTHER + allow_custom_character = GHOSTROLE_TAKE_PREFS_APPEARANCE -/obj/effect/mob_spawn/ghost_role/human/fugitive/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/fugitive/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() var/datum/antagonist/fugitive_hunter/fughunter = new fughunter.backstory = back_story diff --git a/code/modules/mob_spawn/ghost_roles/golem_roles.dm b/code/modules/mob_spawn/ghost_roles/golem_roles.dm index af7cde7320d..f1a25d1e151 100644 --- a/code/modules/mob_spawn/ghost_roles/golem_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/golem_roles.dm @@ -32,14 +32,7 @@ notify_flags = NOTIFY_CATEGORY_NOFLASH, ) -/obj/effect/mob_spawn/ghost_role/human/golem/name_mob(mob/living/spawned_mob, forced_name) - if(forced_name || !iscarbon(spawned_mob)) - return ..() - - forced_name = generate_random_name_species_based(spawned_mob.gender, TRUE, species_type = /datum/species/golem) - return ..() - -/obj/effect/mob_spawn/ghost_role/human/golem/special(mob/living/new_spawn, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/golem/special(mob/living/new_spawn, mob/mob_possessor, apply_prefs) . = ..() if(is_path_in_list(initial_type, GLOB.golem_stack_food_directory)) var/datum/golem_food_buff/initial_buff = GLOB.golem_stack_food_directory[initial_type] @@ -73,7 +66,7 @@ desc = "A humanoid shape, empty, lifeless, and full of potential." prompt_name = "a free golem" -/obj/effect/mob_spawn/ghost_role/human/golem/adamantine/special(mob/living/new_spawn, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/golem/adamantine/special(mob/living/new_spawn, mob/mob_possessor, apply_prefs) . = ..() if(!ishuman(new_spawn)) return @@ -110,8 +103,6 @@ to_chat(new_spawn, span_userdanger("Serve [real_owner.real_name], and assist [real_owner.p_them()] in completing [real_owner.p_their()] goals at any cost.")) /obj/effect/mob_spawn/ghost_role/human/golem/servant/name_mob(mob/living/spawned_mob, forced_name) - if(forced_name || !iscarbon(spawned_mob)) - return ..() if(owner_ref?.resolve()) forced_name = "Golem ([rand(1,999)])" return ..() diff --git a/code/modules/mob_spawn/ghost_roles/mining_roles.dm b/code/modules/mob_spawn/ghost_roles/mining_roles.dm index 9b971338109..1c11cfb0fce 100644 --- a/code/modules/mob_spawn/ghost_roles/mining_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/mining_roles.dm @@ -80,7 +80,7 @@ flavour_text = "Ch'yea. You came here, like, on spring break, hopin' to pick up some bangin' hot chicks, y'knaw?" spawner_job_path = /datum/job/beach_bum outfit = /datum/outfit/beachbum - allow_custom_character = GHOSTROLE_ALLOW_OTHER + allow_custom_character = GHOSTROLE_TAKE_PREFS_APPEARANCE /obj/effect/mob_spawn/ghost_role/human/beach/lifeguard you_are_text = "You're a spunky lifeguard!" @@ -89,7 +89,7 @@ outfit = /datum/outfit/beachbum/lifeguard allow_custom_character = NONE -/obj/effect/mob_spawn/ghost_role/human/beach/lifeguard/special(mob/living/carbon/human/lifeguard, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/beach/lifeguard/special(mob/living/carbon/human/lifeguard, mob/mob_possessor, apply_prefs) . = ..() lifeguard.gender = FEMALE lifeguard.update_body() @@ -237,7 +237,7 @@ to_chat(user, span_warning("You have exhausted your usefulness to the Necropolis.")) return FALSE -/obj/effect/mob_spawn/ghost_role/human/ash_walker/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/ash_walker/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() spawned_human.fully_replace_character_name(null, spawned_human.generate_random_mob_name(TRUE)) to_chat(spawned_human, "Drag the corpses of men and beasts to your nest. It will absorb them to create more of your kind. Invade the strange structure of the outsiders if you must. Do not cause unnecessary destruction, as littering the wastes with ugly wreckage is certain to not gain you favor. Glory to the Necropolis!") @@ -290,7 +290,7 @@ deletes_on_zero_uses_left = FALSE allow_custom_character = ALL -/obj/effect/mob_spawn/ghost_role/human/lavaland_syndicate/special(mob/living/new_spawn, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/lavaland_syndicate/special(mob/living/new_spawn, mob/mob_possessor, apply_prefs) . = ..() new_spawn.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) diff --git a/code/modules/mob_spawn/ghost_roles/space_roles.dm b/code/modules/mob_spawn/ghost_roles/space_roles.dm index 387472b707b..0b1e6c41d3b 100644 --- a/code/modules/mob_spawn/ghost_roles/space_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/space_roles.dm @@ -14,10 +14,12 @@ important_text = "Work as a team with your fellow survivors and do not abandon them." outfit = /datum/outfit/oldeng spawner_job_path = /datum/job/ancient_crew - allow_custom_character = GHOSTROLE_ALLOW_OTHER + allow_custom_character = GHOSTROLE_TAKE_PREFS_APPEARANCE -/obj/effect/mob_spawn/ghost_role/human/oldstation/create_from_ghost(mob/dead/user) +/obj/effect/mob_spawn/ghost_role/human/oldstation/create(mob/mob_possessor, newname, apply_prefs) . = ..() + if(!.) + return notify_ghosts( "Someone just woke up on Charlie Station! Why not join them and help out?", source = ., //the spawned mob diff --git a/code/modules/mob_spawn/ghost_roles/spider_roles.dm b/code/modules/mob_spawn/ghost_roles/spider_roles.dm index 0ee4435a9e4..9251930808f 100644 --- a/code/modules/mob_spawn/ghost_roles/spider_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/spider_roles.dm @@ -139,8 +139,9 @@ if(!silent) to_chat(user, span_warning("\The [src] is not ready to hatch yet!")) return FALSE + return TRUE -/obj/effect/mob_spawn/ghost_role/spider/special(mob/living/basic/spider/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/spider/special(mob/living/basic/spider/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() if (isspider(spawned_mob)) spawned_mob.directive = directive @@ -197,21 +198,12 @@ ) flash_window = TRUE -/** - * Makes a ghost into a spider based on the type of egg cluster. - * - * Allows a ghost to get a prompt to use the egg cluster to become a spider. - * - * Arguments: - * * user - The ghost attempting to become a spider - * * newname - If set, renames the mob to this name - */ -/obj/effect/mob_spawn/ghost_role/spider/create(mob/user, newname) +/obj/effect/mob_spawn/ghost_role/spider/pre_ghost_take(mob/dead/observer/user) var/chosen_spider = length(potentialspawns) > 1 ? get_radial_choice(user) : potentialspawns[1] - if(QDELETED(src) || QDELETED(user) || isnull(chosen_spider)) + if(isnull(chosen_spider)) return FALSE mob_type = chosen_spider - return ..() + return TRUE /// Pick a spider type from a radial menu /obj/effect/mob_spawn/ghost_role/spider/proc/get_radial_choice(mob/user) diff --git a/code/modules/mob_spawn/ghost_roles/unused_roles.dm b/code/modules/mob_spawn/ghost_roles/unused_roles.dm index b94e3159d8a..f001ff2487e 100644 --- a/code/modules/mob_spawn/ghost_roles/unused_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/unused_roles.dm @@ -12,7 +12,7 @@ though fate has other plans for you." flavour_text = "Good. It seems as though your ship crashed. You remember that you were convicted of " spawner_job_path = /datum/job/escaped_prisoner - allow_custom_character = GHOSTROLE_ALLOW_OTHER + allow_custom_character = GHOSTROLE_TAKE_PREFS_APPEARANCE /obj/effect/mob_spawn/ghost_role/human/prisoner_transport/Initialize(mapload) . = ..() @@ -25,7 +25,7 @@ new /obj/structure/fluff/empty_sleeper/syndicate(get_turf(src)) return ..() -/obj/effect/mob_spawn/ghost_role/human/prisoner_transport/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/prisoner_transport/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() spawned_human.fully_replace_character_name(null, "NTP #LL-0[rand(111,999)]") //Nanotrasen Prisoner #Lavaland-(numbers) @@ -157,7 +157,7 @@ new/obj/structure/fluff/empty_sleeper(get_turf(src)) return ..() -/obj/effect/mob_spawn/ghost_role/human/exile/special(mob/living/new_spawn, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/exile/special(mob/living/new_spawn, mob/mob_possessor, apply_prefs) . = ..() new_spawn.fully_replace_character_name(null,"Wish Granter's Victim ([rand(1,999)])") var/wish = rand(1,4) @@ -192,7 +192,7 @@ you_are_text = "You are a Nanotrasen Commander!" flavour_text = "Upper-crusty of Nanotrasen. You should be given the respect you're owed." outfit = /datum/outfit/nanotrasencommander - allow_custom_character = GHOSTROLE_ALLOW_OTHER + allow_custom_character = GHOSTROLE_TAKE_PREFS_APPEARANCE //space doctor, a rat with cancer, and bessie from an old removed lavaland ruin. @@ -234,7 +234,7 @@ icon = 'icons/obj/machines/sleeper.dmi' icon_state = "sleeper" -/obj/effect/mob_spawn/cow/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/cow/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() gender = FEMALE @@ -279,7 +279,7 @@ spawner_job_path = /datum/job/syndicate_cybersun allow_custom_character = ALL -/obj/effect/mob_spawn/ghost_role/human/syndicatespace/special(mob/living/new_spawn, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/human/syndicatespace/special(mob/living/new_spawn, mob/mob_possessor, apply_prefs) . = ..() new_spawn.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) var/datum/job/spawn_job = SSjob.get_job_type(spawner_job_path) diff --git a/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm b/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm index b9a89d0c6dd..04432bf8bd0 100644 --- a/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm +++ b/code/modules/mob_spawn/ghost_roles/venus_human_trap.dm @@ -31,7 +31,7 @@ if(flower_bud.trait_flags & SPACEVINE_COLD_RESISTANT) spawned_human_trap.unsuitable_cold_damage = 0 -/obj/effect/mob_spawn/ghost_role/venus_human_trap/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/venus_human_trap/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.mind.add_antag_datum(/datum/antagonist/venus_human_trap) @@ -54,3 +54,4 @@ if(!silent) to_chat(user, span_warning("\The [src] has not borne fruit yet!")) return FALSE + return TRUE diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index 48b514ea088..51e9e5cb18a 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -44,16 +44,36 @@ QDEL_NULL(outfit) return ..() -/// Creates whatever mob the spawner makes. Return FALSE if we want to exit from here without doing that, returning NULL will be logged to admins. -/obj/effect/mob_spawn/proc/create(mob/mob_possessor, newname) +/** + * Creates whatever mob the spawner makes. + * + * * mob_possessor - The ghost/mob that is possessing this mob, if applicable + * * newname - A forced name for the mob, if applicable + * * apply_prefs - Whether we should apply the possessor's preferences to the mob, if applicable + * + * Returns + * - the created mob + * - CANCEL_SPAWN if the spawn process should be stopped + * - null if the spawn failed (and something went wrong) + */ +/obj/effect/mob_spawn/proc/create(mob/mob_possessor, newname, apply_prefs) + SHOULD_NOT_SLEEP(TRUE) + var/mob/living/spawned_mob = new mob_type(get_turf(src)) //living mobs only + special(spawned_mob, mob_possessor, apply_prefs) name_mob(spawned_mob, newname) - special(spawned_mob, mob_possessor) equip(spawned_mob) spawned_mob_ref = WEAKREF(spawned_mob) return spawned_mob -/obj/effect/mob_spawn/proc/special(mob/living/spawned_mob, mob/mob_possessor) +/** + * Any special behavior that needs to be done to the mob after it's created but before it's equipped. + * + * * spawned_mob - The mob that was created + * * mob_possessor - The ghost/mob that is possessing this mob, if applicable + * * apply_prefs - Whether we should apply the possessor's preferences to the mob, if applicable + */ +/obj/effect/mob_spawn/proc/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) SHOULD_CALL_PARENT(TRUE) if(faction) spawned_mob.faction = faction @@ -111,9 +131,6 @@ var/uses = 1 /// Does the spawner delete itself when it runs out of uses? var/deletes_on_zero_uses_left = TRUE - /// A list of the ckeys that currently are trying to access this spawner, so that they can't try to spawn more than once (in case there's sleeps). - /// Static because you only really want to be able to spawn in one spawner at a time, obviously. - var/static/list/ckeys_trying_to_spawn ///bitflag that determines if players can spawn in as their statics var/allow_custom_character = NONE @@ -154,51 +171,64 @@ //ATTACK GHOST IGNORING PARENT RETURN VALUE /obj/effect/mob_spawn/ghost_role/attack_ghost(mob/dead/observer/user) - if(!SSticker.HasRoundStarted() || !loc) + if(!SSticker.HasRoundStarted() || isnull(loc) || QDELETED(src)) return - // We don't open the prompt more than once at a time. + // Lazylist of the ckeys that currently are trying to access any spawner, so that they can't try to spawn more than once (in case there's sleeps). + var/static/list/ckeys_trying_to_spawn if(LAZYFIND(ckeys_trying_to_spawn, user.ckey)) return + if(uses <= 0 && !infinite_use) + to_chat(user, span_warning("This spawner is out of charges!")) + return FALSE + if(!can_ghost_take(user)) + return FALSE + uses -= 1 // Remove a use EARLY to account for sleep / inputs var/user_ckey = user.ckey // Just in case shenanigans happen, we always want to remove it from the list. LAZYADD(ckeys_trying_to_spawn, user_ckey) + var/prompt_fail = FALSE + var/apply_prefs = FALSE if(prompt_ghost) var/prompt = "Become [prompt_name]?" if(!temp_body && user.can_reenter_corpse && user.mind) prompt += " (Warning, You can no longer be revived!)" - var/ghost_role = tgui_alert(usr, prompt, buttons = list("Yes", "No"), timeout = 10 SECONDS) - if(ghost_role != "Yes" || !loc || QDELETED(user)) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) - return + prompt_fail = tgui_alert(user, prompt, buttons = list("Yes", "No"), timeout = 10 SECONDS) != "Yes" - if(!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER) && !(flags_1 & ADMIN_SPAWNED_1)) - to_chat(user, span_warning("An admin has temporarily disabled non-admin ghost roles!")) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) - return - if(uses <= 0 && !infinite_use) //just in case - to_chat(user, span_warning("This spawner is out of charges!")) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) - return + var/species_pref = user.client.prefs.read_preference(/datum/preference/choiced/species) || /datum/species/human + if(!prompt_fail && user.started_as_observer && allow_custom_character && (GLOB.species_prototypes[species_pref].inherent_respiration_type & RESPIRATION_OXYGEN)) + var/static_prompt = "Because you haven't taken a role so far, you may spawn in as \ + [((allow_custom_character & GHOSTROLE_TAKE_PREFS_SPECIES) || species_pref == /datum/species/human) ? "" : "a human version of"] \ + your customized character with a random name. Would you like to?" + apply_prefs = tgui_alert(user, static_prompt, "Custom Character", list("Yes", "No"), 10 SECONDS) == "Yes" + if(!prompt_fail && !pre_ghost_take(user)) + prompt_fail = TRUE + + if(prompt_fail || !can_ghost_take(user) || !create_from_ghost(user, apply_prefs, subtract_uses = FALSE)) + uses += 1 + LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) + +/// Allows for modifications before the ghost is turned into a mob. +/// You can put sleeps or inputs in here, sanity checking is done for you after this proc returns. +/// Returning FALSE will cancel the spawn process. +/obj/effect/mob_spawn/ghost_role/proc/pre_ghost_take(mob/dead/observer/user) + return TRUE + +/// Checks if a ghost can take this ghost role. +/obj/effect/mob_spawn/ghost_role/proc/can_ghost_take(mob/dead/observer/user) if(is_banned_from(user.ckey, role_ban)) to_chat(user, span_warning("You are banned from this role!")) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) - return - if(!allow_spawn(user, silent = FALSE)) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) - return + return FALL_STOP_INTERCEPTING + if(!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER) && !(flags_1 & ADMIN_SPAWNED_1)) + to_chat(user, span_warning("An admin has temporarily disabled non-admin ghost roles!")) + return FALSE if(QDELETED(src) || QDELETED(user)) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) - return - - if(uses <= 0 && !infinite_use) // Just in case something took longer than it should've and we got here after the uses went below zero. - to_chat(user, span_warning("This spawner is out of charges!")) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) - return - - create_from_ghost(user) + return FALSE + if(!allow_spawn(user, silent = FALSE)) + return FALSE + return TRUE /** * Uses a use and creates a mob from a passed ghost @@ -207,61 +237,53 @@ * * If you are manually forcing a player into this mob spawn, * you should be using this and not directly calling [proc/create]. + * + * * * user - The ghost/mob that is possessing this mob + * * * apply_prefs - Whether we should apply the possessor's preferences to the mob + * * * subtract_uses - Whether to subtract a use from the spawner. + * Set to FALSE if you want to handle uses manually elsewhere. */ -/obj/effect/mob_spawn/ghost_role/proc/create_from_ghost(mob/dead/observer/user) +/obj/effect/mob_spawn/ghost_role/proc/create_from_ghost(mob/dead/observer/user, apply_prefs, subtract_uses = TRUE) + SHOULD_NOT_OVERRIDE(TRUE) + SHOULD_NOT_SLEEP(TRUE) ASSERT(istype(user)) - var/user_ckey = user.ckey // We need to do it before everything else, because after the create() the ckey will already have been transferred. user.log_message("became a [prompt_name].", LOG_GAME) - uses -= 1 // Remove a use before trying to spawn to prevent strangeness like the spawner trying to spawn more mobs than it should be able to if(!temp_body) user.mind = null // dissassociate mind, don't let it follow us to the next life - var/species_pref = user.client.prefs.read_preference(/datum/preference/choiced/species) - var/datum/species/user_species = GLOB.species_prototypes[species_pref] - if(user_species.inherent_respiration_type == RESPIRATION_PLASMA) // Stupid flammable skeletons... - user.started_as_observer = null - else if(user.started_as_observer && allow_custom_character) - var/static_prompt = "Because you haven't taken a role so far, you may spawn in as [(allow_custom_character & GHOSTROLE_ALLOW_SPECIES || user_species == /datum/species/human) ? "" : "a human version of"] your customized character with a random name. Would you like to?" - var/static_prompt_result = tgui_alert(user, static_prompt, "Custom Character", list("Yes", "No"), 10 SECONDS) - if(static_prompt_result != "Yes") - user.started_as_observer = null - var/created = create(user) - LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) // We do this AFTER the create() so that we're basically sure that the user won't be in their ghost body anymore, so they can't click on the spawner again. - - if(!created) - uses += 1 // Refund use because we didn't actually spawn anything - - if(isnull(created)) // If we explicitly return FALSE instead of just not returning a mob, we don't want to spam the admins - CRASH("An instance of [type] didn't return anything when creating a mob, this might be broken!") - - SEND_SIGNAL(src, COMSIG_GHOSTROLE_SPAWNED, created) - check_uses() // Now we check if the spawner should delete itself or not + var/mob/created = create(user, apply_prefs = apply_prefs) + if(ismob(created)) + SEND_SIGNAL(src, COMSIG_GHOSTROLE_SPAWNED, created) + if(subtract_uses) + uses -= 1 + check_uses() + else if(isnull(created)) // null instead of explicit CANCEL_SPAWN means something went wrong + CRASH("An instance of [type] didn't return anything when creating a mob, this might be broken!") return created -/obj/effect/mob_spawn/ghost_role/create(mob/mob_possessor, newname) +/obj/effect/mob_spawn/ghost_role/create(mob/mob_possessor, newname, apply_prefs) if(!mob_possessor.key) // This is in the scenario that the server is somehow lagging, or someone fucked up their code, and we try to spawn the same person in twice. We'll simply not spawn anything and CRASH(), so that we report what happened. CRASH("Attempted to create an instance of [type] with a mob that had no ckey attached to it, which isn't supported by ghost role spawners!") return ..() - -/obj/effect/mob_spawn/ghost_role/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/ghost_role/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() if(mob_possessor) - var/mob/dead/observer/observer_possessor = mob_possessor - if(observer_possessor.started_as_observer) - var/old_name = spawned_mob.real_name - if(allow_custom_character & GHOSTROLE_ALLOW_OTHER) - mob_possessor.client.prefs.safe_transfer_prefs_to(spawned_mob) - spawned_mob.fully_replace_character_name(newname = old_name) - if(!(allow_custom_character & GHOSTROLE_ALLOW_SPECIES || ishumanbasic(spawned_mob))) - spawned_mob.set_species(/datum/species/human) + if(mob_possessor.client && apply_prefs && allow_custom_character && ishuman(spawned_mob)) + var/mob/living/carbon/human/spawned_human = spawned_mob + if(allow_custom_character & GHOSTROLE_TAKE_PREFS_APPEARANCE) + mob_possessor.client.prefs.apply_prefs_to(spawned_human, icon_updates = TRUE, do_not_apply = typesof(/datum/preference/name, /datum/preference/choiced/species)) + if(allow_custom_character & GHOSTROLE_TAKE_PREFS_SPECIES) + spawned_human.set_species(mob_possessor.client.prefs.read_preference(/datum/preference/choiced/species)) + spawned_human.fully_replace_character_name(spawned_human.real_name, spawned_human.generate_random_mob_name()) if(mob_possessor.mind) mob_possessor.mind.transfer_to(spawned_mob, force_key_move = TRUE) else spawned_mob.PossessByPlayer(mob_possessor.key) + var/datum/mind/spawned_mind = spawned_mob.mind if(spawned_mind) spawned_mob.mind.set_assigned_role_with_greeting(SSjob.get_job_type(spawner_job_path)) @@ -314,7 +336,7 @@ if(mapload || (SSticker && SSticker.current_state > GAME_STATE_SETTING_UP)) INVOKE_ASYNC(src, PROC_REF(create)) -/obj/effect/mob_spawn/corpse/special(mob/living/spawned_mob, mob/mob_possessor) +/obj/effect/mob_spawn/corpse/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) . = ..() spawned_mob.death(TRUE) spawned_mob.adjustOxyLoss(oxy_damage) @@ -323,7 +345,7 @@ if (corpse_description) spawned_mob.AddComponent(/datum/component/temporary_description, corpse_description, naive_corpse_description) -/obj/effect/mob_spawn/corpse/create(mob/mob_possessor, newname) +/obj/effect/mob_spawn/corpse/create(mob/mob_possessor, newname, apply_prefs) . = ..() qdel(src) @@ -343,7 +365,7 @@ ///husks the corpse if true. var/husk = FALSE -/obj/effect/mob_spawn/corpse/human/special(mob/living/carbon/human/spawned_human) +/obj/effect/mob_spawn/corpse/human/special(mob/living/carbon/human/spawned_human, mob/mob_possessor, apply_prefs) . = ..() if(husk) spawned_human.Drain()