Merge remote-tracking branch 'tgstation/master' into upstream-2025-11-29

# Conflicts:
#	_maps/RandomRuins/SpaceRuins/derelict_sulaco.dmm
#	_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm
#	_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm
#	_maps/map_files/tramstation/tramstation.dmm
#	code/_onclick/hud/new_player.dm
#	code/datums/components/squashable.dm
#	code/datums/diseases/advance/symptoms/heal.dm
#	code/datums/diseases/chronic_illness.dm
#	code/datums/status_effects/buffs.dm
#	code/datums/status_effects/debuffs/drunk.dm
#	code/datums/status_effects/debuffs/stamcrit.dm
#	code/game/machinery/computer/crew.dm
#	code/game/objects/items/devices/scanners/health_analyzer.dm
#	code/game/objects/items/wall_mounted.dm
#	code/game/turfs/closed/indestructible.dm
#	code/modules/admin/view_variables/filterrific.dm
#	code/modules/antagonists/heretic/influences.dm
#	code/modules/cargo/orderconsole.dm
#	code/modules/client/preferences.dm
#	code/modules/events/space_vines/vine_mutations.dm
#	code/modules/mob/dead/new_player/new_player.dm
#	code/modules/mob/living/carbon/human/death.dm
#	code/modules/mob/living/carbon/human/species_types/jellypeople.dm
#	code/modules/mob/living/damage_procs.dm
#	code/modules/mob/living/living.dm
#	code/modules/mob_spawn/ghost_roles/mining_roles.dm
#	code/modules/mob_spawn/mob_spawn.dm
#	code/modules/projectiles/ammunition/energy/laser.dm
#	code/modules/projectiles/guns/ballistic/launchers.dm
#	code/modules/projectiles/guns/energy/laser.dm
#	code/modules/reagents/chemistry/machinery/chem_dispenser.dm
#	code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
#	code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
#	code/modules/reagents/chemistry/reagents/medicine_reagents.dm
#	code/modules/surgery/healing.dm
#	code/modules/unit_tests/designs.dm
#	icons/mob/inhands/items_lefthand.dmi
#	icons/mob/inhands/items_righthand.dmi
#	tgui/packages/tgui/interfaces/ChemDispenser.tsx
This commit is contained in:
nevimer
2025-11-29 22:49:21 -05:00
1185 changed files with 39068 additions and 32028 deletions
+100 -61
View File
@@ -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
@@ -112,9 +132,8 @@
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
////descriptions
@@ -152,16 +171,25 @@
//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
// SKYRAT EDIT ADDITION START
if(restricted_species && !(user.client?.prefs?.read_preference(/datum/preference/choiced/species) in restricted_species))
var/incorrect_species = tgui_alert(user, "Current species preference incompatible, proceed with random appearance?", "Incompatible Species", list("Yes", "No"))
@@ -174,43 +202,46 @@
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
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
// SKYRAT EDIT ADDITION START
if(is_banned_from(user.ckey, BAN_GHOST_ROLE_SPAWNER)) // Ghost role bans
to_chat(user, span_warning("Error, you are banned from playing ghost roles!"))
LAZYREMOVE(ckeys_trying_to_spawn, user_ckey)
return
return FALSE
// SKYRAT EDIT ADDITION END
if(!allow_spawn(user, silent = FALSE))
LAZYREMOVE(ckeys_trying_to_spawn, user_ckey)
return
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
@@ -219,49 +250,57 @@
*
* 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/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/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)
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))
spawned_mind.name = spawned_mob.real_name
if(show_flavor)
var/output_message = span_infoplain("<span class='big bold'>[you_are_text]</span>")
if(flavour_text != "")
@@ -310,16 +349,16 @@
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)
spawned_mob.adjustBruteLoss(brute_damage)
spawned_mob.adjustFireLoss(burn_damage)
spawned_mob.adjust_oxy_loss(oxy_damage)
spawned_mob.adjust_brute_loss(brute_damage)
spawned_mob.adjust_fire_loss(burn_damage)
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)
@@ -339,7 +378,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()