From c3d08e5da9c7e705c64efa7416368c56b72a9d0d Mon Sep 17 00:00:00 2001 From: Fghj240 <43589874+Fghj240@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:33:16 -0800 Subject: [PATCH] You can spawn in as your static when taking a ghost role (#93809) ## About The Pull Request When take a ghost role from a ghost role sleeper, if you became a ghost by observing you will be offered the opportunity to spawn in as your static. Obviously if you joined the round normally or have already spawned in as a ghost role you won't be prompted. No cloning yourself allowed. Not every ghost role allows this, and you won't get the prompt if your static is a race that breathes plasma (basically just those purple skeletons) because that opens up a giant can of worms. However, you _will_ get the prompt if you are playing as any other race. (I'm pretty sure every ghost role has power for ethereals besides ashwalkers but that's not one of the ghost roles that lets you spawn as your static for super obvious reasons) Some ghost roles, like pirates, will allow you to spawn in as your static but overwrite your name. ## Why It's Good For The Game Felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent felinid comms agent ## Changelog :cl: add: You may now spawn in as your static when picking a ghost role /:cl: --------- Co-authored-by: Fghj240 --- code/__DEFINES/_flags.dm | 4 ++++ code/_globalvars/bitfields.dm | 5 +++++ .../antagonists/pirate/pirate_roles.dm | 4 ++++ code/modules/bitrunning/spawners.dm | 3 +++ .../ghost_roles/fugitive_hunter_roles.dm | 1 + .../mob_spawn/ghost_roles/mining_roles.dm | 5 +++++ .../mob_spawn/ghost_roles/space_roles.dm | 4 ++++ .../mob_spawn/ghost_roles/unused_roles.dm | 10 +++++++++ code/modules/mob_spawn/mob_spawn.dm | 22 +++++++++++++++++-- 9 files changed, 56 insertions(+), 2 deletions(-) diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm index 383edbe3e81..6fcac62c8cb 100644 --- a/code/__DEFINES/_flags.dm +++ b/code/__DEFINES/_flags.dm @@ -356,3 +356,7 @@ 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/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 205d51029a1..3ffe30aeec9 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -689,3 +689,8 @@ DEFINE_BITFIELD(visible_info, list( "EMPATH_SEE_COLD" = EMPATH_SEE_COLD, "EMPATH_SEE_EVIL" = EMPATH_SEE_EVIL, )) + +DEFINE_BITFIELD(allow_custom_character, list( + "GHOSTROLE_ALLOW_SPECIES" = GHOSTROLE_ALLOW_SPECIES, + "GHOSTROLE_ALLOW_OTHER" = GHOSTROLE_ALLOW_OTHER, +)) diff --git a/code/modules/antagonists/pirate/pirate_roles.dm b/code/modules/antagonists/pirate/pirate_roles.dm index 9d7cf14cbbb..f2e76c6aee6 100644 --- a/code/modules/antagonists/pirate/pirate_roles.dm +++ b/code/modules/antagonists/pirate/pirate_roles.dm @@ -14,6 +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 ///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. @@ -58,6 +59,7 @@ outfit = /datum/outfit/pirate rank = "Mate" fluff_spawn = null + allow_custom_character = NONE /obj/effect/mob_spawn/ghost_role/human/pirate/skeleton/captain rank = "Captain" @@ -75,6 +77,7 @@ mob_species = /datum/species/lizard/silverscale outfit = /datum/outfit/pirate/silverscale rank = "High-born" + allow_custom_character = NONE /obj/effect/mob_spawn/ghost_role/human/pirate/silverscale/generate_pirate_name(spawn_gender) var/first_name @@ -184,6 +187,7 @@ mob_species = /datum/species/ethereal/lustrous outfit = /datum/outfit/pirate/lustrous rank = "Scintillant" + allow_custom_character = NONE /obj/effect/mob_spawn/ghost_role/human/pirate/lustrous/captain rank = "Radiant" diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm index 69e6cae07a0..fad6da01e59 100644 --- a/code/modules/bitrunning/spawners.dm +++ b/code/modules/bitrunning/spawners.dm @@ -34,12 +34,14 @@ outfit = /datum/outfit/beachbum spawner_job_path = /datum/job/beach_bum antag = FALSE + allow_custom_character = GHOSTROLE_ALLOW_OTHER /obj/effect/mob_spawn/ghost_role/human/virtual_domain/beach/lifeguard name = "virtual lifeguard sleeper" you_are_text = "You are a spunky virtual lifeguard!" flavour_text = "It's up to you to make sure nobody lags or gets eaten by malware and stuff." 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) . = ..() @@ -51,6 +53,7 @@ you_are_text = "You are a virtual beach bartender!" flavour_text = "Your job is to keep the virtually rendered drinks coming, and help the dudebros engage drunkness simulations." outfit = /datum/outfit/spacebartender + allow_custom_character = ALL //Skeleton Pirates /obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate 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 2d1f3c7a706..a07d17ec60c 100644 --- a/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm @@ -6,6 +6,7 @@ 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 /obj/effect/mob_spawn/ghost_role/human/fugitive/special(mob/living/carbon/human/spawned_human, mob/mob_possessor) . = ..() diff --git a/code/modules/mob_spawn/ghost_roles/mining_roles.dm b/code/modules/mob_spawn/ghost_roles/mining_roles.dm index 84e95326d2d..9b971338109 100644 --- a/code/modules/mob_spawn/ghost_roles/mining_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/mining_roles.dm @@ -14,6 +14,7 @@ the hostile creatures, and the ash drakes swooping down from the cloudless skies, all you can wish for is the feel of soft grass between your toes and \ the fresh air of Earth. These thoughts are dispelled by yet another recollection of how you got here... " spawner_job_path = /datum/job/hermit + allow_custom_character = ALL /obj/effect/mob_spawn/ghost_role/human/hermit/Initialize(mapload) . = ..() @@ -79,12 +80,14 @@ 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 /obj/effect/mob_spawn/ghost_role/human/beach/lifeguard you_are_text = "You're a spunky lifeguard!" flavour_text = "It's up to you to make sure nobody drowns or gets eaten by sharks and stuff." name = "lifeguard sleeper" 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) . = ..() @@ -119,6 +122,7 @@ flavour_text = "Time to mix drinks and change lives. Smoking space drugs makes it easier to understand your patrons' odd dialect." spawner_job_path = /datum/job/space_bartender outfit = /datum/outfit/spacebartender + allow_custom_character = ALL /datum/outfit/spacebartender name = "Space Bartender" @@ -284,6 +288,7 @@ outfit = /datum/outfit/lavaland_syndicate spawner_job_path = /datum/job/lavaland_syndicate 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) . = ..() diff --git a/code/modules/mob_spawn/ghost_roles/space_roles.dm b/code/modules/mob_spawn/ghost_roles/space_roles.dm index 86650f541b3..387472b707b 100644 --- a/code/modules/mob_spawn/ghost_roles/space_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/space_roles.dm @@ -14,6 +14,7 @@ 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 /obj/effect/mob_spawn/ghost_role/human/oldstation/create_from_ghost(mob/dead/user) . = ..() @@ -102,6 +103,7 @@ outfit = /datum/outfit/syndicate_empty/battlecruiser spawner_job_path = /datum/job/battlecruiser_crew uses = 4 + allow_custom_character = ALL /// The antag team to apply the player to var/datum/team/antag_team @@ -191,6 +193,7 @@ important_text = "Work as a team with your fellow actors and the director to make entertainment for the masses." outfit = /datum/outfit/actor spawner_job_path = /datum/job/ghost_role + allow_custom_character = ALL /datum/outfit/actor name = "Actor" @@ -215,6 +218,7 @@ important_text = "Work as a team with your fellow actors and the director to make entertainment for the masses." outfit = /datum/outfit/actor/director spawner_job_path = /datum/job/ghost_role + allow_custom_character = ALL /datum/outfit/actor/director name = "Director" diff --git a/code/modules/mob_spawn/ghost_roles/unused_roles.dm b/code/modules/mob_spawn/ghost_roles/unused_roles.dm index 1ceef16d559..b94e3159d8a 100644 --- a/code/modules/mob_spawn/ghost_roles/unused_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/unused_roles.dm @@ -12,6 +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 /obj/effect/mob_spawn/ghost_role/human/prisoner_transport/Initialize(mapload) . = ..() @@ -50,6 +51,7 @@ flavour_text = "Cater to visiting guests with your fellow staff, advertise the hotel, and make sure the manager doesn't fire you. Remember, the customer is always right!" important_text = "Do NOT leave the hotel, as that is grounds for contract termination." spawner_job_path = /datum/job/hotel_staff + allow_custom_character = ALL /datum/outfit/hotelstaff name = "Hotel Staff" @@ -94,6 +96,7 @@ flavour_text = "You have awoken, without instruction. Death to Nanotrasen! If there are some clues around as to what you're supposed to be doing, you best follow those." outfit = /datum/outfit/syndicate_empty spawner_job_path = /datum/job/space_syndicate + allow_custom_character = ALL /datum/outfit/syndicate_empty name = "Syndicate Operative Empty" @@ -119,6 +122,7 @@ flavour_text = "Hang out at the bar and chat with your buddies. Feel free to hop back in the cryogenics when you're done chatting." outfit = /datum/outfit/cryobartender spawner_job_path = /datum/job/space_bar_patron + allow_custom_character = ALL /obj/effect/mob_spawn/ghost_role/human/space_bar_patron/attack_hand(mob/user, list/modifiers) var/despawn = tgui_alert(usr, "Return to cryosleep? (Warning, Your mob will be deleted!)", null, list("Yes", "No")) @@ -178,6 +182,7 @@ you_are_text = "You are a Nanotrasen Private Security Officer!" flavour_text = "If higher command has an assignment for you, it's best you follow that. Otherwise, death to The Syndicate." outfit = /datum/outfit/nanotrasensoldier + allow_custom_character = ALL /obj/effect/mob_spawn/ghost_role/human/commander name = "sleeper" @@ -187,6 +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 //space doctor, a rat with cancer, and bessie from an old removed lavaland ruin. @@ -199,6 +205,7 @@ flavour_text = "It's your job- no, your duty as a doctor, to care and heal those in need." outfit = /datum/outfit/job/doctor spawner_job_path = /datum/job/space_doctor + allow_custom_character = ALL /obj/effect/mob_spawn/ghost_role/human/doctor/alive/equip(mob/living/carbon/human/doctor) . = ..() @@ -244,6 +251,7 @@ flavour_text = "Monitor Nanotrasen communications and record information. All intruders should be disposed of \ swiftly to assure no gathered information is stolen or lost. Try not to wander too far from the outpost as the \ caves can be a deadly place even for a trained operative such as yourself." + allow_custom_character = ALL /datum/outfit/snowsyndie name = "Syndicate Snow Operative" @@ -269,6 +277,7 @@ important_text = "Obey orders given by your captain. DO NOT let the ship fall into enemy hands." outfit = /datum/outfit/syndicatespace/syndicrew 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) . = ..() @@ -286,6 +295,7 @@ important_text = "Protect the ship and secret documents in your backpack with your own life." outfit = /datum/outfit/syndicatespace/syndicaptain spawner_job_path = /datum/job/syndicate_cybersun_captain + allow_custom_character = ALL /obj/effect/mob_spawn/ghost_role/human/syndicatespace/captain/Destroy() new /obj/structure/fluff/empty_sleeper/syndicate/captain(get_turf(src)) diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index bdb565cc9bc..48b514ea088 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -114,6 +114,8 @@ /// 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 ////descriptions @@ -206,7 +208,7 @@ * If you are manually forcing a player into this mob spawn, * you should be using this and not directly calling [proc/create]. */ -/obj/effect/mob_spawn/ghost_role/proc/create_from_ghost(mob/dead/user) +/obj/effect/mob_spawn/ghost_role/proc/create_from_ghost(mob/dead/observer/user) 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. @@ -214,6 +216,15 @@ 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. @@ -239,6 +250,14 @@ /obj/effect/mob_spawn/ghost_role/special(mob/living/spawned_mob, mob/mob_possessor) . = ..() 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.mind) mob_possessor.mind.transfer_to(spawned_mob, force_key_move = TRUE) else @@ -247,7 +266,6 @@ if(spawned_mind) spawned_mob.mind.set_assigned_role_with_greeting(SSjob.get_job_type(spawner_job_path)) spawned_mind.name = spawned_mob.real_name - if(show_flavor) var/output_message = span_infoplain("[you_are_text]") if(flavour_text != "")