diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm index ecba4221fc..3a95ba1db5 100644 --- a/code/datums/chat_message.dm +++ b/code/datums/chat_message.dm @@ -78,12 +78,11 @@ var/list/runechat_image_cache = list() if(timer_delete) deltimer(timer_delete) timer_delete = null - if(!QDELETED(owned_by)) + if(istype(owned_by, /client)) // hopefully the PARENT_QDELETING on client should beat this if it's a disconnect UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) - LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) + if(owned_by.seen_messages) + LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) owned_by.images.Remove(message) - if(!QDELETED(message_loc)) - UnregisterSignal(message_loc, COMSIG_PARENT_QDELETING) owned_by = null message_loc = null message = null @@ -108,7 +107,7 @@ var/list/runechat_image_cache = list() // Register client who owns this message owned_by = owner.client - RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(qdel_self)) + RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(unregister_qdel_self)) // this should only call owned_by if the client is destroyed var/extra_length = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) var/maxlen = extra_length ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH @@ -217,6 +216,10 @@ var/list/runechat_image_cache = list() spawn(lifespan - CHAT_MESSAGE_EOL_FADE) end_of_life() +/datum/chatmessage/proc/unregister_qdel_self() // this should only call owned_by if the client is destroyed + UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) + owned_by = null + qdel_self() /** * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion */ diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 5ce7fcbc58..38b48ee76a 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -473,70 +473,74 @@ SStranscore.leave_round(to_despawn) //VOREStation Edit End - Resleeving. - //Handle job slot/tater cleanup. - var/job = to_despawn.mind.assigned_role - job_master.FreeRole(job) - to_despawn.mind.assigned_role = null + // Everything below should only be applicable to a cliented living/carbon/human. + // All living/carbon/humans should have minds. - if(to_despawn.mind.objectives.len) - qdel(to_despawn.mind.objectives) - to_despawn.mind.special_role = null + //Handle job slot/tater cleanup. + var/job = to_despawn.mind.assigned_role + job_master.FreeRole(job) + to_despawn.mind.assigned_role = null - //else - //if(ticker.mode.name == "AutoTraitor") - //var/datum/game_mode/traitor/autotraitor/current_mode = ticker.mode - //current_mode.possible_traitors.Remove(to_despawn) + if(to_despawn.mind.objectives.len) + qdel(to_despawn.mind.objectives) + to_despawn.mind.special_role = null - // Delete them from datacore. + //else + //if(ticker.mode.name == "AutoTraitor") + //var/datum/game_mode/traitor/autotraitor/current_mode = ticker.mode + //current_mode.possible_traitors.Remove(to_despawn) - if(PDA_Manifest.len) - PDA_Manifest.Cut() - for(var/datum/data/record/R in data_core.medical) - if((R.fields["name"] == to_despawn.real_name)) - qdel(R) - for(var/datum/data/record/T in data_core.security) - if((T.fields["name"] == to_despawn.real_name)) - qdel(T) - for(var/datum/data/record/G in data_core.general) - if((G.fields["name"] == to_despawn.real_name)) - qdel(G) + // Delete them from datacore. - // Also check the hidden version of each datacore, if they're an offmap role. - var/datum/job/J = SSjob.get_job(job) - if(J?.offmap_spawn) - for(var/datum/data/record/R in data_core.hidden_general) + if(PDA_Manifest.len) + PDA_Manifest.Cut() + for(var/datum/data/record/R in data_core.medical) if((R.fields["name"] == to_despawn.real_name)) qdel(R) - for(var/datum/data/record/T in data_core.hidden_security) + for(var/datum/data/record/T in data_core.security) if((T.fields["name"] == to_despawn.real_name)) qdel(T) - for(var/datum/data/record/G in data_core.hidden_medical) + for(var/datum/data/record/G in data_core.general) if((G.fields["name"] == to_despawn.real_name)) qdel(G) - icon_state = base_icon_state + // Also check the hidden version of each datacore, if they're an offmap role. + var/datum/job/J = SSjob.get_job(job) + if(J?.offmap_spawn) + for(var/datum/data/record/R in data_core.hidden_general) + if((R.fields["name"] == to_despawn.real_name)) + qdel(R) + for(var/datum/data/record/T in data_core.hidden_security) + if((T.fields["name"] == to_despawn.real_name)) + qdel(T) + for(var/datum/data/record/G in data_core.hidden_medical) + if((G.fields["name"] == to_despawn.real_name)) + qdel(G) - //TODO: Check objectives/mode, update new targets if this mob is the target, spawn new antags? + icon_state = base_icon_state + + //TODO: Check objectives/mode, update new targets if this mob is the target, spawn new antags? - //Make an announcement and log the person entering storage. - control_computer.frozen_crew += "[to_despawn.real_name], [to_despawn.mind.role_alt_title] - [stationtime2text()]" - control_computer._admin_logs += "[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) at [stationtime2text()]" - log_and_message_admins("[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) entered cryostorage.") + //Make an announcement and log the person entering storage. + control_computer.frozen_crew += "[to_despawn.real_name], [to_despawn.mind.role_alt_title] - [stationtime2text()]" + control_computer._admin_logs += "[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) at [stationtime2text()]" + log_and_message_admins("[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) entered cryostorage.") - //VOREStation Edit Start - var/depart_announce = TRUE - var/departing_job = to_despawn.mind.role_alt_title + //VOREStation Edit Start + var/depart_announce = TRUE + var/departing_job = to_despawn.mind.role_alt_title - if(istype(to_despawn, /mob/living/dominated_brain)) - depart_announce = FALSE + if(istype(to_despawn, /mob/living/dominated_brain)) + depart_announce = FALSE - if(depart_announce) - announce.autosay("[to_despawn.real_name][departing_job ? ", [departing_job], " : " "][on_store_message]", "[on_store_name]", announce_channel, using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)) - visible_message("\The [initial(name)] [on_store_visible_message_1] [to_despawn.real_name] [on_store_visible_message_2]", 3) + if(depart_announce) + announce.autosay("[to_despawn.real_name][departing_job ? ", [departing_job], " : " "][on_store_message]", "[on_store_name]", announce_channel, using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)) + visible_message("\The [initial(name)] [on_store_visible_message_1] [to_despawn.real_name] [on_store_visible_message_2]", 3) - //VOREStation Edit End + + //VOREStation Edit End //VOREStation Edit begin: Dont delete mobs-in-mobs if(to_despawn.client && to_despawn.stat<2) diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm index 749bb65173..d2fa720e6a 100644 --- a/code/game/objects/items/devices/text_to_speech.dm +++ b/code/game/objects/items/devices/text_to_speech.dm @@ -26,7 +26,7 @@ if(message) audible_message("[icon2html(src, user.client)] \The [src.name] states, \"[message]\"", runemessage = "synthesized speech") if(ismob(loc)) - loc.audible_message("", runemessage = "\[TTS Voice\] [message]") + loc.runechat_message("\[TTS Voice\] [message]") /obj/item/device/text_to_speech/AltClick(mob/user) // QOL Change attack_self(user) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 0f053b73ee..f3b7c76106 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -271,19 +271,30 @@ //DISCONNECT// ////////////// /client/Del() + if(!gc_destroyed) + gc_destroyed = world.time + if (!QDELING(src)) + stack_trace("Client does not purport to be QDELING, this is going to cause bugs in other places!") + + GLOB.ahelp_tickets.ClientLogout(src) + GLOB.mhelp_tickets.ClientLogout(src) + + // Yes this is the same as what's found in qdel(). Yes it does need to be here + // Get off my back + SEND_SIGNAL(src, COMSIG_PARENT_QDELETING, TRUE) + Destroy() //Clean up signals and timers. + return ..() + +/client/Destroy() if(holder) holder.owner = null GLOB.admins -= src if (mentorholder) mentorholder.owner = null GLOB.mentors -= src - GLOB.ahelp_tickets.ClientLogout(src) - GLOB.mhelp_tickets.ClientLogout(src) GLOB.directory -= ckey GLOB.clients -= src - return ..() -/client/Destroy() ..() return QDEL_HINT_HARDDEL_NOW diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm index 6d35219a1d..71a442415a 100644 --- a/code/modules/client/preference_setup/general/03_body.dm +++ b/code/modules/client/preference_setup/general/03_body.dm @@ -418,7 +418,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O for(var/BP in mark_datum.body_parts) var/obj/item/organ/external/O = character.organs_by_name[BP] - if(O) + if(O && islist(O.markings) && islist(pref.body_markings[M]) && islist(pref.body_markings[M][BP])) O.markings[M] = list("color" = pref.body_markings[M][BP]["color"], "datum" = mark_datum, "priority" = priority, "on" = pref.body_markings[M][BP]["on"]) character.markings_len = priority diff --git a/code/modules/client/preference_setup/global/02_settings.dm b/code/modules/client/preference_setup/global/02_settings.dm index fcdb85b843..71ff5d0a09 100644 --- a/code/modules/client/preference_setup/global/02_settings.dm +++ b/code/modules/client/preference_setup/global/02_settings.dm @@ -91,7 +91,7 @@ preference = list(preference) for(var/p in preference) var/datum/client_preference/cp = get_client_preference(p) - if(!prefs || !cp || !(cp.key in prefs.preferences_enabled)) + if(!prefs || !cp || !istype(cp, /datum/client_preference) || !(cp.key in prefs.preferences_enabled)) return FALSE return TRUE diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm index f336a45b86..37ef332a14 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm @@ -124,6 +124,8 @@ I think I covered everything. var/small_icon = 'icons/mob/bigdragon_small.dmi' var/small_icon_state = "dragon_small" var/flames + var/firebreathtimer + var/chargetimer tame_items = list( /obj/item/weapon/coin/gold = 100, @@ -263,6 +265,10 @@ I think I covered everything. /mob/living/simple_mob/vore/bigdragon/runechat_y_offset(width, height) return (..()*size_multiplier) + 40 +/mob/living/simple_mob/vore/bigdragon/death() + . = ..() + canceltimers() + /// /// Verbs /// @@ -766,10 +772,14 @@ I think I covered everything. do_windup_animation(A, charge_warmup) //callbacks are more reliable than byond's process scheduler - addtimer(CALLBACK(src, PROC_REF(chargeend), A), charge_warmup) + chargetimer = addtimer(CALLBACK(src, PROC_REF(chargeend), A), charge_warmup, TIMER_STOPPABLE) /mob/living/simple_mob/vore/bigdragon/proc/chargeend(var/atom/A, var/explicit = 0, var/gentle = 0) + //make sure our target still exists and is on a turf + if(QDELETED(A) || !isturf(get_turf(A))) + set_AI_busy(FALSE) + return status_flags |= LEAPING flying = 1 //So we can thunk into things hovering = 1 // So we don't hurt ourselves running off cliffs @@ -804,10 +814,14 @@ I think I covered everything. set_AI_busy(TRUE) flames = 1 build_icons() - addtimer(CALLBACK(src, PROC_REF(firebreathend), A), charge_warmup) + firebreathtimer = addtimer(CALLBACK(src, PROC_REF(firebreathend), A), charge_warmup, TIMER_STOPPABLE) playsound(src, "sound/magic/Fireball.ogg", 50, 1) /mob/living/simple_mob/vore/bigdragon/proc/firebreathend(var/atom/A) + //make sure our target still exists and is on a turf + if(QDELETED(A) || !isturf(get_turf(A))) + set_AI_busy(FALSE) + return var/obj/item/projectile/P = new /obj/item/projectile/bullet/dragon(get_turf(src)) src.visible_message("\The [src] spews fire at \the [A]!") playsound(src, "sound/weapons/Flamer.ogg", 50, 1) @@ -891,6 +905,9 @@ I think I covered everything. vore_selected = gut2 //Just incase it eats someone right after being tamed ai_holder = new /datum/ai_holder/simple_mob/healbelly/retaliate/dragon(src) + //Cancel any charges or firebreaths winding up + canceltimers() + /datum/ai_holder/simple_mob/healbelly intelligence_level = 3 can_breakthrough = 0 @@ -1036,6 +1053,17 @@ I think I covered everything. vore_selected = gut1 D.give_target(attacker) +/mob/living/simple_mob/vore/bigdragon/proc/canceltimers() + //Cancel any charges or firebreaths winding up + if(firebreathtimer) + deltimer(firebreathtimer) + firebreathtimer = null + if(chargetimer) + deltimer(chargetimer) + chargetimer = null + //re-enable the AI + set_AI_busy(FALSE) + //Smack people it warns /datum/ai_holder/simple_mob/healbelly/retaliate/dragon/proc/dissuade(var/chump) if(chump in check_trajectory(chump, holder, pass_flags = PASSTABLE)) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 9efeca0e58..643ad28105 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1220,12 +1220,24 @@ return 0 //Exploitable Info Update +/obj + var/datum/weakref/exploit_for //if this obj is an exploit for somebody, this points to them /mob/proc/amend_exploitable(var/obj/item/I) if(istype(I)) exploit_addons |= I var/exploitmsg = html_decode("\n" + "Has " + I.name + ".") exploit_record += exploitmsg + I.exploit_for = WEAKREF(src) + + +/obj/Destroy() + if(exploit_for) + var/mob/exploited = exploit_for.resolve() + exploited?.exploit_addons -= src + exploit_for = null + . = ..() + /obj/Destroy() diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 2dbe340951..42dcae7dcc 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -89,7 +89,7 @@ * return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists. */ /datum/tgui/proc/open() - if(!user.client) + if(!user?.client) return FALSE if(window) return FALSE @@ -214,7 +214,7 @@ * optional force bool Send an update even if UI is not interactive. */ /datum/tgui/proc/send_full_update(custom_data, force) - if(!user.client || !initialized || closing) + if(!user?.client || !initialized || closing) return //if(!COOLDOWN_FINISHED(src, refresh_cooldown)) //refreshing = TRUE