diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 40aea24105..6474b6c28d 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -121,8 +121,13 @@ #define COMSIG_MOVABLE_HEAR "movable_hear" //from base of atom/movable/Hear(): (message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) #define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source) +// /mind signals +#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to(): (new_character, old_character) + // /mob signals #define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed) +#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize() (can_reenter_corpse) + #define COMPONENT_BLOCK_GHOSTING 1 #define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj #define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (magic, holy, protection_sources) #define COMPONENT_BLOCK_MAGIC 1 @@ -132,6 +137,7 @@ #define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters) #define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params) #define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target) +#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey() #define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): () #define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list) #define COMPONENT_UPPERCASE_SPEECH 1 @@ -159,6 +165,8 @@ #define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag) #define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value) +// /machinery signals +#define COMSIG_MACHINE_EJECT_OCCUPANT "eject_occupant" //from base of obj/machinery/dropContents() (occupant) // /obj/item signals #define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index cc0bd3e0b4..19d0755085 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -511,7 +511,7 @@ G_found.client.prefs.copy_to(new_character) new_character.dna.update_dna_identity() - new_character.key = G_found.key + G_found.transfer_ckey(new_character, FALSE) return new_character diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 1bc7d8e3be..5bbc5de4a5 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -46,7 +46,7 @@ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) - friend.key = C.key + C.transfer_ckey(friend, FALSE) friend_initialized = TRUE else qdel(src) diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm index 653b8a98c0..1a26ea7a14 100644 --- a/code/datums/brain_damage/split_personality.dm +++ b/code/datums/brain_damage/split_personality.dm @@ -26,7 +26,7 @@ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) - stranger_backseat.key = C.key + C.transfer_ckey(stranger_backseat, FALSE) log_game("[key_name(stranger_backseat)] became [key_name(owner)]'s split personality.") message_admins("[ADMIN_LOOKUPFLW(stranger_backseat)] became [ADMIN_LOOKUPFLW(owner)]'s split personality.") else @@ -184,7 +184,7 @@ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s brainwashed mind?", null, null, null, 75, stranger_backseat) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) - stranger_backseat.key = C.key + C.transfer_ckey(stranger_backseat, FALSE) else qdel(src) diff --git a/code/datums/components/virtual_reality.dm b/code/datums/components/virtual_reality.dm new file mode 100644 index 0000000000..750cc045ce --- /dev/null +++ b/code/datums/components/virtual_reality.dm @@ -0,0 +1,128 @@ +/datum/component/virtual_reality + dupe_mode = COMPONENT_DUPE_ALLOWED //mindswap memes, shouldn't stack up otherwise. + var/datum/mind/mastermind // where is my mind t. pixies + var/datum/mind/current_mind + var/obj/machinery/vr_sleeper/vr_sleeper + var/datum/action/quit_vr/quit_action + var/you_die_in_the_game_you_die_for_real = FALSE + var/datum/component/virtual_reality/inception //The component works on a very fragile link betwixt mind, ckey and death. + +/datum/component/virtual_reality/Initialize(mob/M, obj/machinery/vr_sleeper/gaming_pod, yolo = FALSE, new_char = TRUE) + if(!ismob(parent) || !istype(M)) + return COMPONENT_INCOMPATIBLE + var/mob/vr_M = parent + mastermind = M.mind + RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED), .proc/game_over) + RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/switch_player) + RegisterSignal(mastermind, COMSIG_MIND_TRANSFER, .proc/switch_player) + you_die_in_the_game_you_die_for_real = yolo + quit_action = new() + if(gaming_pod) + vr_sleeper = gaming_pod + RegisterSignal(vr_sleeper, COMSIG_ATOM_EMAG_ACT, .proc/you_only_live_once) + RegisterSignal(vr_sleeper, COMSIG_MACHINE_EJECT_OCCUPANT, .proc/revert_to_reality) + vr_M.ckey = M.ckey + var/datum/component/virtual_reality/clusterfk = M.GetComponent(/datum/component/virtual_reality) + if(clusterfk && !clusterfk.inception) + clusterfk.inception = src + SStgui.close_user_uis(M, src) + +/datum/component/virtual_reality/RegisterWithParent() + var/mob/M = parent + current_mind = M.mind + quit_action.Grant(M) + RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/revert_to_reality) + RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED), .proc/game_over) + RegisterSignal(M, COMSIG_MOB_GHOSTIZE, .proc/be_a_quitter) + RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/pass_me_the_remote) + RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/pass_me_the_remote) + mastermind.current.audiovisual_redirect = M + if(vr_sleeper) + vr_sleeper.vr_mob = M + +/datum/component/virtual_reality/UnregisterFromParent() + quit_action.Remove(parent) + UnregisterSignal(parent, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED, COMSIG_MOB_KEY_CHANGE, COMSIG_MOB_GHOSTIZE)) + UnregisterSignal(current_mind, COMSIG_MIND_TRANSFER) + UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER) + current_mind = null + mastermind.current.audiovisual_redirect = null + +/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob) + if(vr_sleeper || !new_mob.mind) + // Machineries currently don't deal up with the occupant being polymorphed et similar... Or did something fuck up? + revert_to_reality() + return + old_mob.audiovisual_redirect = null + new_mob.audiovisual_redirect = parent + +/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source) + if(source != quit_action) + return COMPONENT_ACTION_BLOCK_TRIGGER + revert_to_reality(signal_source) + +/datum/component/virtual_reality/proc/you_only_live_once() + if(you_die_in_the_game_you_die_for_real || vr_sleeper?.only_current_user_can_interact) + return FALSE + you_die_in_the_game_you_die_for_real = TRUE + return TRUE + +/datum/component/virtual_reality/proc/pass_me_the_remote(datum/source, mob/new_mob) + if(new_mob == mastermind.current) + revert_to_reality(source) + return TRUE + new_mob.TakeComponent(src) + return TRUE + +/datum/component/virtual_reality/PostTransfer() + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/virtual_reality/proc/revert_to_reality(datum/source) + quit_it() + +/datum/component/virtual_reality/proc/game_over(datum/source) + quit_it(TRUE, TRUE) + +/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse) + quit_it() + return COMPONENT_BLOCK_GHOSTING + +/datum/component/virtual_reality/proc/virtual_reality_in_a_virtual_reality(mob/player, killme = FALSE, datum/component/virtual_reality/yo_dawg) + var/mob/M = parent + quit_it(FALSE, killme, player, yo_dawg) + yo_dawg.inception = null + if(killme) + M.death(FALSE) + +/datum/component/virtual_reality/proc/quit_it(deathcheck = FALSE, cleanup = FALSE, mob/override) + var/mob/M = parent + var/mob/dreamer = override ? override : mastermind.current + if(!mastermind) + to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...") + else + var/key_transfer = FALSE + if(inception?.parent) + inception.virtual_reality_in_a_virtual_reality(dreamer, cleanup, src) + else + key_transfer = TRUE + if(key_transfer) + M.transfer_ckey(dreamer, FALSE) + dreamer.stop_sound_channel(CHANNEL_HEARTBEAT) + dreamer.audiovisual_redirect = null + if(deathcheck && you_die_in_the_game_you_die_for_real) + to_chat(mastermind, "You feel everything fading away...") + dreamer.death(FALSE) + if(cleanup) + var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M) + if(cleanbot) + LAZYADD(cleanbot.corpse_party, M) + if(vr_sleeper) + vr_sleeper.vr_mob = null + vr_sleeper = null + qdel(src) + +/datum/component/virtual_reality/Destroy() + var/datum/action/quit_vr/delet_me = quit_action + . = ..() + qdel(delet_me) \ No newline at end of file diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index b295f5f10b..083a1f9c6c 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -67,7 +67,7 @@ if(affected_mob.mind) affected_mob.mind.transfer_to(new_mob) else - new_mob.key = affected_mob.key + affected_mob.transfer_ckey(new_mob) new_mob.name = affected_mob.real_name new_mob.real_name = new_mob.name @@ -82,7 +82,7 @@ to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbaned player.") affected_mob.ghostize(0) - affected_mob.key = C.key + C.transfer_ckey(affected_mob) else to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!") new_mob.death() diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 8e61801e15..68efe93254 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -19,9 +19,9 @@ - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting - a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. + a ghost to become a xeno during an event), use this mob proc. - new_mob.key = key + mob.transfer_ckey(new_mob) The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple! However if you want that mind to have any special properties like being a traitor etc you will have to do that @@ -89,6 +89,7 @@ return language_holder /datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0) + var/old_character = current if(current) // remove ourself from our old body's mind variable current.mind = null SStgui.on_transfer(current, new_character) @@ -99,7 +100,7 @@ if(key) if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours - new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless. + new_character.ghostize(TRUE, TRUE) //we'll need to ghostize so that key isn't mobless. else key = new_character.key @@ -123,6 +124,7 @@ transfer_martial_arts(new_character) if(active || force_key_move) new_character.key = key //now transfer the key to link the client to our new body + SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character) //CIT CHANGE - makes arousal update when transfering bodies if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs. diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 2acef4f06b..4da085d5a9 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -8,7 +8,7 @@ ruletype = "Midround" /// If the ruleset should be restricted from ghost roles. var/restrict_ghost_roles = TRUE - /// What type the ruleset is restricted to. + /// What type the ruleset is restricted to. var/required_type = /mob/living/carbon/human var/list/living_players = list() var/list/living_antags = list() @@ -101,7 +101,7 @@ log_game("DYNAMIC: Polling [possible_volunteers.len] players to apply for the [name] ruleset.") candidates = pollGhostCandidates("The mode is looking for volunteers to become [antag_flag] for [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300) - + if(!candidates || candidates.len <= 0) message_admins("The ruleset [name] received no applications.") log_game("DYNAMIC: The ruleset [name] received no applications.") @@ -194,7 +194,7 @@ ..() for(var/mob/living/player in living_players) if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon. - living_players -= player + living_players -= player continue if(is_centcom_level(player.z)) living_players -= player // We don't autotator people in CentCom @@ -407,7 +407,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/xenomorph/generate_ruleset_body(mob/applicant) var/obj/vent = pick_n_take(vents) var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) - new_xeno.key = applicant.key + applicant.transfer_ckey(new_xeno, FALSE) message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.") log_game("DYNAMIC: [key_name(new_xeno)] was spawned as an alien by the midround ruleset.") return new_xeno diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index ada77f3b3e..667c1f6054 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -186,6 +186,7 @@ Class Procs: if(isliving(A)) var/mob/living/L = A L.update_canmove() + SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant) occupant = null /obj/machinery/proc/can_be_occupant(atom/movable/am) @@ -498,6 +499,7 @@ Class Procs: /obj/machinery/Exited(atom/movable/AM, atom/newloc) . = ..() if (AM == occupant) + SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant) occupant = null /obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8 diff --git a/code/game/machinery/exp_cloner.dm b/code/game/machinery/exp_cloner.dm index e8364d2271..4116e18f59 100644 --- a/code/game/machinery/exp_cloner.dm +++ b/code/game/machinery/exp_cloner.dm @@ -52,7 +52,7 @@ var/list/candidates = pollCandidatesForMob("Do you want to play as [clonename]'s defective clone?", null, null, null, 100, H) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) - H.key = C.key + C.transfer_ckey(H) if(grab_ghost_when == CLONER_FRESH_CLONE) H.grab_ghost() diff --git a/code/modules/VR/vr_human.dm b/code/modules/VR/vr_human.dm deleted file mode 100644 index 53a4bbe540..0000000000 --- a/code/modules/VR/vr_human.dm +++ /dev/null @@ -1,61 +0,0 @@ -/mob/living/carbon/human/virtual_reality - var/datum/mind/real_mind // where is my mind t. pixies - var/obj/machinery/vr_sleeper/vr_sleeper - var/datum/action/quit_vr/quit_action - -/mob/living/carbon/human/virtual_reality/Initialize() - . = ..() - quit_action = new() - quit_action.Grant(src) - -/mob/living/carbon/human/virtual_reality/death() - revert_to_reality() - ..() - -/mob/living/carbon/human/virtual_reality/Destroy() - revert_to_reality() - return ..() - -/mob/living/carbon/human/virtual_reality/Life() - . = ..() - if(real_mind) - var/mob/living/real_me = real_mind.current - if (real_me && real_me.stat == CONSCIOUS) - return - revert_to_reality(FALSE) - -/mob/living/carbon/human/virtual_reality/ghostize() - stack_trace("Ghostize was called on a virtual reality mob") - -/mob/living/carbon/human/virtual_reality/ghost() - set category = "OOC" - set name = "Ghost" - set desc = "Relinquish your life and enter the land of the dead." - revert_to_reality(FALSE) - -/mob/living/carbon/human/virtual_reality/proc/revert_to_reality(deathchecks = TRUE) - if(real_mind && mind) - real_mind.current.audiovisual_redirect = null - real_mind.current.ckey = ckey - real_mind.current.stop_sound_channel(CHANNEL_HEARTBEAT) - if(deathchecks && vr_sleeper) - if(vr_sleeper.you_die_in_the_game_you_die_for_real) - to_chat(real_mind, "You feel everything fading away...") - real_mind.current.death(0) - if(deathchecks && vr_sleeper) - vr_sleeper.vr_human = null - vr_sleeper = null - real_mind = null - -/datum/action/quit_vr - name = "Quit Virtual Reality" - icon_icon = 'icons/mob/actions/actions_vr.dmi' - button_icon_state = "logout" - -/datum/action/quit_vr/Trigger() - if(..()) - if(istype(owner, /mob/living/carbon/human/virtual_reality)) - var/mob/living/carbon/human/virtual_reality/VR = owner - VR.revert_to_reality(FALSE) - else - Remove(owner) diff --git a/code/modules/VR/vr_mob.dm b/code/modules/VR/vr_mob.dm new file mode 100644 index 0000000000..5c0cea9f60 --- /dev/null +++ b/code/modules/VR/vr_mob.dm @@ -0,0 +1,44 @@ +/mob/proc/build_virtual_character(mob/M) + mind_initialize() + if(!M) + return FALSE + name = M.name + real_name = M.real_name + mind.name = M.real_name + return TRUE + +/mob/living/carbon/build_virtual_character(mob/M) + . = ..() + if(!.) + return + if(iscarbon(M)) + var/mob/living/carbon/C = M + C.dna?.transfer_identity(src) + +/mob/living/carbon/human/build_virtual_character(mob/M, datum/outfit/outfit) + . = ..() + if(!.) + return + var/mob/living/carbon/human/H + if(ishuman(M)) + H = M + socks = H ? H.socks : random_socks() + socks_color = H ? H.socks_color : random_color() + undershirt = H ? H.undershirt : random_undershirt(M.gender) + shirt_color = H ? H.shirt_color : random_color() + underwear = H ? H.underwear : random_underwear(M.gender) + undie_color = H ? H.undie_color : random_color() + give_genitals(TRUE) + if(outfit) + var/datum/outfit/O = new outfit() + O.equip(src) + +/datum/action/quit_vr + name = "Quit Virtual Reality" + icon_icon = 'icons/mob/actions/actions_vr.dmi' + button_icon_state = "logout" + +/datum/action/quit_vr/Trigger() //this merely a trigger for /datum/component/virtual_reality + . = ..() + if(!.) + Remove(owner) diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm index 89e9c33ecc..72cbdc1409 100644 --- a/code/modules/VR/vr_sleeper.dm +++ b/code/modules/VR/vr_sleeper.dm @@ -1,5 +1,3 @@ - - //Glorified teleporter that puts you in a new human body. // it's """VR""" /obj/machinery/vr_sleeper @@ -12,9 +10,10 @@ circuit = /obj/item/circuitboard/machine/vr_sleeper var/you_die_in_the_game_you_die_for_real = FALSE var/datum/effect_system/spark_spread/sparks - var/mob/living/carbon/human/virtual_reality/vr_human + var/mob/living/vr_mob + var/virtual_mob_type = /mob/living/carbon/human var/vr_category = "default" //Specific category of spawn points to pick from - var/allow_creating_vr_humans = TRUE //So you can have vr_sleepers that always spawn you as a specific person or 1 life/chance vr games + var/allow_creating_vr_mobs = TRUE //So you can have vr_sleepers that always spawn you as a specific person or 1 life/chance vr games var/only_current_user_can_interact = FALSE /obj/machinery/vr_sleeper/Initialize() @@ -44,7 +43,7 @@ /obj/machinery/vr_sleeper/Destroy() open_machine() - cleanup_vr_human() + cleanup_vr_mob() QDEL_NULL(sparks) return ..() @@ -58,8 +57,9 @@ /obj/machinery/vr_sleeper/emag_act(mob/user) . = ..() - if(you_die_in_the_game_you_die_for_real) + if(!(obj_flags & EMAGGED)) return + obj_flags |= EMAGGED you_die_in_the_game_you_die_for_real = TRUE sparks.start() addtimer(CALLBACK(src, .proc/emagNotify), 150) @@ -69,12 +69,11 @@ icon_state = "[initial(icon_state)][state_open ? "-open" : ""]" /obj/machinery/vr_sleeper/open_machine() - if(!state_open) - if(vr_human) - vr_human.revert_to_reality(FALSE) - if(occupant) - SStgui.close_user_uis(occupant, src) - ..() + if(state_open) + return + if(occupant) + SStgui.close_user_uis(occupant, src) + return ..() /obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user) if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) @@ -94,22 +93,20 @@ if("vr_connect") var/mob/living/carbon/human/human_occupant = occupant if(human_occupant && human_occupant.mind && usr == occupant) + to_chat(occupant, "Transferring to virtual reality...") - if(vr_human && vr_human.stat == CONSCIOUS && !vr_human.real_mind) - SStgui.close_user_uis(occupant, src) - human_occupant.audiovisual_redirect = vr_human - vr_human.real_mind = human_occupant.mind - vr_human.ckey = human_occupant.ckey - to_chat(vr_human, "Transfer successful! You are now playing as [vr_human] in VR!") + if(vr_mob && (!istype(vr_mob) || !vr_mob.InCritical()) && !vr_mob.GetComponent(/datum/component/virtual_reality)) + vr_mob.AddComponent(/datum/component/virtual_reality, human_occupant, src, you_die_in_the_game_you_die_for_real) + to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!") else - if(allow_creating_vr_humans) + if(allow_creating_vr_mobs) to_chat(occupant, "Virtual avatar not found, attempting to create one...") var/obj/effect/landmark/vr_spawn/V = get_vr_spawnpoint() var/turf/T = get_turf(V) if(T) SStgui.close_user_uis(occupant, src) - build_virtual_human(occupant, T, V.vr_outfit) - to_chat(vr_human, "Transfer successful! You are now playing as [vr_human] in VR!") + new_player(occupant, T, V.vr_outfit) + to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!") else to_chat(occupant, "Virtual world misconfigured, aborting transfer") else @@ -117,8 +114,8 @@ . = TRUE if("delete_avatar") if(!occupant || usr == occupant) - if(vr_human) - cleanup_vr_human() + if(vr_mob) + cleanup_vr_mob() else to_chat(usr, "The VR Sleeper's safeties prevent you from doing that.") . = TRUE @@ -131,19 +128,22 @@ /obj/machinery/vr_sleeper/ui_data(mob/user) var/list/data = list() - if(vr_human && !QDELETED(vr_human)) + if(vr_mob && !QDELETED(vr_mob)) data["can_delete_avatar"] = TRUE - var/status - switch(vr_human.stat) - if(CONSCIOUS) - status = "Conscious" - if(DEAD) - status = "Dead" - if(UNCONSCIOUS) - status = "Unconscious" - if(SOFT_CRIT) - status = "Barely Conscious" - data["vr_avatar"] = list("name" = vr_human.name, "status" = status, "health" = vr_human.health, "maxhealth" = vr_human.maxHealth) + data["vr_avatar"] = list("name" = vr_mob.name) + data["isliving"] = istype(vr_mob) + if(data["isliving"]) + var/status + switch(vr_mob.stat) + if(CONSCIOUS) + status = "Conscious" + if(DEAD) + status = "Dead" + if(UNCONSCIOUS) + status = "Unconscious" + if(SOFT_CRIT) + status = "Barely Conscious" + data["vr_avatar"] += list("status" = status, "health" = vr_mob.health, "maxhealth" = vr_mob.maxHealth) data["toggle_open"] = state_open data["emagged"] = you_die_in_the_game_you_die_for_real data["isoccupant"] = (user == occupant) @@ -157,40 +157,25 @@ for(var/obj/effect/landmark/vr_spawn/V in GLOB.landmarks_list) GLOB.vr_spawnpoints[V.vr_category] = V -/obj/machinery/vr_sleeper/proc/build_virtual_human(mob/living/carbon/human/H, location, var/datum/outfit/outfit, transfer = TRUE) - if(H) - cleanup_vr_human() - vr_human = new /mob/living/carbon/human/virtual_reality(location) - vr_human.mind_initialize() - vr_human.vr_sleeper = src - vr_human.real_mind = H.mind - H.dna.transfer_identity(vr_human) - vr_human.name = H.name - vr_human.real_name = H.real_name - vr_human.socks = H.socks - vr_human.socks_color = H.socks_color - vr_human.undershirt = H.undershirt - vr_human.shirt_color = H.shirt_color - vr_human.underwear = H.underwear - vr_human.undie_color = H.undie_color - vr_human.updateappearance(TRUE, TRUE, TRUE) - vr_human.give_genitals(TRUE) //CITADEL ADD - if(outfit) - var/datum/outfit/O = new outfit() - O.equip(vr_human) - if(transfer && H.mind) - SStgui.close_user_uis(H, src) - H.audiovisual_redirect = vr_human - vr_human.ckey = H.ckey +/obj/machinery/vr_sleeper/proc/new_player(mob/living/carbon/human/H, location, datum/outfit/outfit, transfer = TRUE) + if(!H) + return + cleanup_vr_mob() + vr_mob = new virtual_mob_type(location) + if(vr_mob.build_virtual_character(H, outfit)) + var/mob/living/carbon/human/vr_H = vr_mob + vr_H.updateappearance(TRUE, TRUE, TRUE) + if(!transfer || !H.mind) + return + vr_mob.AddComponent(/datum/component/virtual_reality, H, src, you_die_in_the_game_you_die_for_real) -/obj/machinery/vr_sleeper/proc/cleanup_vr_human() - if(vr_human) - vr_human.vr_sleeper = null // Prevents race condition where a new human could get created out of order and set to null. - QDEL_NULL(vr_human) +/obj/machinery/vr_sleeper/proc/cleanup_vr_mob() + if(vr_mob) + QDEL_NULL(vr_mob) /obj/machinery/vr_sleeper/proc/emagNotify() - if(vr_human) - vr_human.Dizzy(10) + if(vr_mob) + vr_mob.Dizzy(10) /obj/effect/landmark/vr_spawn //places you can spawn in VR, auto selected by the vr_sleeper during get_vr_spawnpoint() var/vr_category = "default" //So we can have specific sleepers, eg: "Basketball VR Sleeper", etc. @@ -222,6 +207,7 @@ color = "#00FF00" invisibility = INVISIBILITY_ABSTRACT var/area/vr_area + var/list/corpse_party /obj/effect/vr_clean_master/Initialize() . = ..() @@ -234,7 +220,8 @@ qdel(casing) for(var/obj/effect/decal/cleanable/C in vr_area) qdel(C) - for (var/mob/living/carbon/human/virtual_reality/H in vr_area) - if (H.stat == DEAD && !H.vr_sleeper && !H.real_mind) - qdel(H) + for (var/A in corpse_party) + var/mob/M = A + if(get_area(M) == vr_area && M.stat == DEAD) + qdel(M) addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES) diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm index da811a1974..81050e6eae 100644 --- a/code/modules/admin/fun_balloon.dm +++ b/code/modules/admin/fun_balloon.dm @@ -61,7 +61,7 @@ to_chat(body, "Your mob has been taken over by a ghost!") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(body)])") body.ghostize(0) - body.key = C.key + C.transfer_ckey(body) new /obj/effect/temp_visual/gravpush(get_turf(body)) /obj/effect/fun_balloon/sentience/emergency_shuttle diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/secrets.dm index 9dc8b73d0e..58fd627c74 100644 --- a/code/modules/admin/secrets.dm +++ b/code/modules/admin/secrets.dm @@ -744,7 +744,7 @@ var/mob/chosen = players[1] if (chosen.client) chosen.client.prefs.copy_to(spawnedMob) - spawnedMob.key = chosen.key + chosen.transfer_ckey(spawnedMob) players -= chosen if (ishuman(spawnedMob) && ispath(humanoutfit, /datum/outfit)) var/mob/living/carbon/human/H = spawnedMob diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index e15613c43d..53fdb315b5 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -306,7 +306,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) var/mob/living/silicon/pai/pai = new(card) pai.name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text pai.real_name = pai.name - pai.key = choice.key + choice.transfer_ckey(pai) card.setPersonality(pai) for(var/datum/paiCandidate/candidate in SSpai.candidates) if(candidate.key == choice.key) diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index d9732818bd..09a8f692d8 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -412,7 +412,7 @@ //Spawn the body var/mob/living/carbon/human/ERTOperative = new ertemplate.mobtype(spawnloc) chosen_candidate.client.prefs.copy_to(ERTOperative) - ERTOperative.key = chosen_candidate.key + chosen_candidate.transfer_ckey(ERTOperative) if(ertemplate.enforce_human || ERTOperative.dna.species.dangerous_existence) // Don't want any exploding plasmemes ERTOperative.set_species(/datum/species/human) diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index c27b355ff1..246ccb1d07 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -384,7 +384,7 @@ Traitors and the like can also be revived with the previous role mostly intact. //Now to give them their mind back. G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_xeno.key = G_found.key + G_found.transfer_ckey(new_xeno, FALSE) to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") var/msg = "[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno." message_admins(msg) @@ -397,7 +397,7 @@ Traitors and the like can also be revived with the previous role mostly intact. var/mob/living/carbon/monkey/new_monkey = new SSjob.SendToLateJoin(new_monkey) G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_monkey.key = G_found.key + G_found.transfer_ckey(new_monkey, FALSE) to_chat(new_monkey, "You have been fully respawned. Enjoy the game.") var/msg = "[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno." message_admins(msg) @@ -437,7 +437,7 @@ Traitors and the like can also be revived with the previous role mostly intact. if(!new_character.mind.assigned_role) new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role. - new_character.key = G_found.key + G_found.transfer_ckey(new_character, FALSE) /* The code below functions with the assumption that the mob is already a traitor if they have a special role. diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index aa91af1dd8..42719e28bd 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -15,7 +15,7 @@ GLOBAL_LIST_EMPTY(antagonists) var/antag_memory = ""//These will be removed with antag datum var/antag_moodlet //typepath of moodlet that the mob will gain with their status var/can_hijack = HIJACK_NEUTRAL //If these antags are alone on shuttle hijack happens. - + //Antag panel properties var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind var/antagpanel_category = "Uncategorized" //Antagpanel will display these together, REQUIRED @@ -87,7 +87,7 @@ GLOBAL_LIST_EMPTY(antagonists) to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner.current)]) to replace a jobbaned player.") owner.current.ghostize(0) - owner.current.key = C.key + C.transfer_ckey(owner.current, FALSE) /datum/antagonist/proc/on_removal() remove_innate_effects() diff --git a/code/modules/antagonists/blob/blob/powers.dm b/code/modules/antagonists/blob/blob/powers.dm index 9e915ee0fa..e49d186362 100644 --- a/code/modules/antagonists/blob/blob/powers.dm +++ b/code/modules/antagonists/blob/blob/powers.dm @@ -172,7 +172,7 @@ blobber.adjustHealth(blobber.maxHealth * 0.5) blob_mobs += blobber var/mob/dead/observer/C = pick(candidates) - blobber.key = C.key + C.transfer_ckey(blobber) SEND_SOUND(blobber, sound('sound/effects/blobattack.ogg')) SEND_SOUND(blobber, sound('sound/effects/attackblob.ogg')) to_chat(blobber, "You are a blobbernaut!") diff --git a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm index 97d7f0c128..2018067b77 100644 --- a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm +++ b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm @@ -349,7 +349,7 @@ to_chat(L, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form!") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(L)]) to replace an inactive clock cultist.") L.ghostize(0) - L.key = C.key + C.transfer_ckey(L, FALSE) var/obj/effect/temp_visual/ratvar/sigil/vitality/V = new /obj/effect/temp_visual/ratvar/sigil/vitality(get_turf(src)) animate(V, alpha = 0, transform = matrix()*2, time = 8) playsound(L, 'sound/magic/staff_healing.ogg', 50, 1) diff --git a/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm b/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm index 12af249bee..2be0fdde11 100644 --- a/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm +++ b/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm @@ -55,7 +55,7 @@ pre_spawn() visible_message(creation_message) var/mob/living/construct = new construct_type(get_turf(src)) - construct.key = user.key + user.transfer_ckey(construct, FALSE) post_spawn(construct) qdel(user) qdel(src) diff --git a/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm b/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm index 05f3ca5917..c01c7f0f57 100644 --- a/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm +++ b/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm @@ -64,7 +64,7 @@ message_admins("Admin [key_name_admin(user)] directly became the Eminence of the cult!") log_admin("Admin [key_name(user)] made themselves the Eminence.") var/mob/camera/eminence/eminence = new(get_turf(src)) - eminence.key = user.key + user.transfer_ckey(eminence, FALSE) hierophant_message("Ratvar has directly assigned the Eminence!") for(var/mob/M in servants_and_ghosts()) M.playsound_local(M, 'sound/machines/clockcult/eminence_selected.ogg', 50, FALSE) @@ -138,7 +138,7 @@ playsound(src, 'sound/machines/clockcult/ark_damage.ogg', 50, FALSE) var/mob/camera/eminence/eminence = new(get_turf(src)) eminence_nominee = pick(candidates) - eminence.key = eminence_nominee.key + eminence_nominee.transfer_ckey(eminence) hierophant_message("A ghost has ascended into the Eminence!") for(var/mob/M in servants_and_ghosts()) M.playsound_local(M, 'sound/machines/clockcult/eminence_selected.ogg', 50, FALSE) diff --git a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm index 9341a7ee6a..c17885315f 100644 --- a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm +++ b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm @@ -45,7 +45,7 @@ return FALSE var/mob/living/simple_animal/drone/cogscarab/ratvar/R = new/mob/living/simple_animal/drone/cogscarab/ratvar(get_turf(src)) R.visible_message("[R] forms, and its eyes blink open, glowing bright red!") - R.key = O.key + O.transfer_ckey(R, FALSE) /obj/structure/destructible/clockwork/massive/ratvar/Bump(atom/A) var/turf/T = get_turf(A) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index bfc4955f68..2e233d26e4 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -575,7 +575,7 @@ structure_check() searches for nearby cultist structures required for the invoca to_chat(mob_to_revive.mind, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form.") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.") mob_to_revive.ghostize(0) - mob_to_revive.key = C.key + C.transfer_ckey(mob_to_revive, FALSE) else fail_invoke() return @@ -870,7 +870,7 @@ structure_check() searches for nearby cultist structures required for the invoca visible_message("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo":""]man.") to_chat(user, "Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely...") var/obj/structure/emergency_shield/invoker/N = new(T) - new_human.key = ghost_to_spawn.key + ghost_to_spawn.transfer_ckey(new_human, FALSE) SSticker.mode.add_cultist(new_human.mind, 0) to_chat(new_human, "You are a servant of the Geometer. You have been made semi-corporeal by the cult of Nar'Sie, and you are to serve them at all costs.") diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm index 1df81a797b..afe439f02c 100644 --- a/code/modules/antagonists/devil/true_devil/_true_devil.dm +++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm @@ -146,7 +146,7 @@ /mob/living/carbon/true_devil/attack_ghost(mob/dead/observer/user as mob) if(ascended || user.mind.soulOwner == src.mind) var/mob/living/simple_animal/imp/S = new(get_turf(loc)) - S.key = user.key + user.transfer_ckey(S, FALSE) S.mind.assigned_role = "Imp" S.mind.special_role = "Imp" var/datum/objective/newobjective = new diff --git a/code/modules/antagonists/disease/disease_event.dm b/code/modules/antagonists/disease/disease_event.dm index 7183ed5455..385cee998b 100644 --- a/code/modules/antagonists/disease/disease_event.dm +++ b/code/modules/antagonists/disease/disease_event.dm @@ -18,7 +18,7 @@ var/mob/dead/observer/selected = pick_n_take(candidates) var/mob/camera/disease/virus = new /mob/camera/disease(SSmapping.get_station_center()) - virus.key = selected.key + selected.transfer_ckey(virus, FALSE) INVOKE_ASYNC(virus, /mob/camera/disease/proc/pick_name) message_admins("[ADMIN_LOOKUPFLW(virus)] has been made into a sentient disease by an event.") log_game("[key_name(virus)] was spawned as a sentient disease by an event.") diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm index 87794993a7..f380fa68e9 100644 --- a/code/modules/antagonists/revenant/revenant.dm +++ b/code/modules/antagonists/revenant/revenant.dm @@ -377,14 +377,15 @@ /obj/item/ectoplasm/revenant/proc/reform() if(QDELETED(src) || QDELETED(revenant) || inert) return - var/key_of_revenant + var/key_of_revenant = FALSE message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.") forceMove(drop_location()) //In case it's in a backpack or someone's hand revenant.forceMove(loc) if(old_key) for(var/mob/M in GLOB.dead_mob_list) if(M.client && M.client.key == old_key) //Only recreates the mob if the mob the client is in is dead - key_of_revenant = old_key + M.transfer_ckey(revenant.key, FALSE) + key_of_revenant = TRUE break if(!key_of_revenant) message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") @@ -396,22 +397,21 @@ visible_message("[src] settles down and seems lifeless.") return var/mob/dead/observer/C = pick(candidates) - key_of_revenant = C.key - if(!key_of_revenant) + C.transfer_ckey(revenant.key, FALSE) + if(!revenant.key) qdel(revenant) message_admins("No ckey was found for the new revenant. Oh well!") inert = TRUE visible_message("[src] settles down and seems lifeless.") return - message_admins("[key_of_revenant] has been [old_key == key_of_revenant ? "re":""]made into a revenant by reforming ectoplasm.") - log_game("[key_of_revenant] was [old_key == key_of_revenant ? "re":""]made as a revenant by reforming ectoplasm.") + message_admins("[key_of_revenant] has been [old_key == revenant.key ? "re":""]made into a revenant by reforming ectoplasm.") + log_game("[key_of_revenant] was [old_key == revenant.key ? "re":""]made as a revenant by reforming ectoplasm.") visible_message("[src] suddenly rises into the air before fading away.") revenant.essence = essence revenant.essence_regen_cap = essence revenant.death_reset() - revenant.key = key_of_revenant revenant = null qdel(src) diff --git a/code/modules/antagonists/revenant/revenant_spawn_event.dm b/code/modules/antagonists/revenant/revenant_spawn_event.dm index c9a892cd64..cb534b6613 100644 --- a/code/modules/antagonists/revenant/revenant_spawn_event.dm +++ b/code/modules/antagonists/revenant/revenant_spawn_event.dm @@ -52,7 +52,7 @@ return MAP_ERROR var/mob/living/simple_animal/revenant/revvie = new(pick(spawn_locs)) - revvie.key = selected.key + selected.transfer_ckey(revvie, FALSE) message_admins("[ADMIN_LOOKUPFLW(revvie)] has been made into a revenant by an event.") log_game("[key_name(revvie)] was spawned as a revenant by an event.") spawned_mobs += revvie diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 40551ae2fc..1df8e43316 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -218,7 +218,7 @@ newstruct.master = stoner var/datum/action/innate/seek_master/SM = new() SM.Grant(newstruct) - newstruct.key = target.key + target.transfer_ckey(newstruct) var/obj/screen/alert/bloodsense/BS if(newstruct.mind && ((stoner && iscultist(stoner)) || cultoverride) && SSticker && SSticker.mode) SSticker.mode.add_cultist(newstruct.mind, 0) @@ -243,7 +243,7 @@ S.canmove = FALSE//Can't move out of the soul stone S.name = "Shade of [T.real_name]" S.real_name = "Shade of [T.real_name]" - S.key = T.key + T.transfer_ckey(S) S.language_holder = U.language_holder.copy(S) if(U) S.faction |= "[REF(U)]" //Add the master as a faction, allowing inter-mob cooperation diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm index 41434f7a04..b7b21bbb9b 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -133,7 +133,7 @@ var/mob/dead/observer/C = pick(candidates) message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender") current_wizard.ghostize() // on the off chance braindead defender gets back in - current_wizard.key = C.key + C.transfer_ckey(current_wizard, FALSE) /obj/structure/academy_wizard_spawner/proc/summon_wizard() var/turf/T = src.loc @@ -272,7 +272,7 @@ if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") - H.key = C.key + C.transfer_ckey(H, FALSE) var/obj/effect/proc_holder/spell/targeted/summonmob/S = new S.target_mob = H diff --git a/code/modules/events/holiday/xmas.dm b/code/modules/events/holiday/xmas.dm index a76c75dd43..5f9873c387 100644 --- a/code/modules/events/holiday/xmas.dm +++ b/code/modules/events/holiday/xmas.dm @@ -77,7 +77,7 @@ if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) santa = new /mob/living/carbon/human(pick(GLOB.blobstart)) - santa.key = C.key + C.transfer_ckey(santa, FALSE) santa.equipOutfit(/datum/outfit/santa) santa.update_icons() diff --git a/code/modules/events/sentience.dm b/code/modules/events/sentience.dm index 55d8ce8b14..96b6cded58 100644 --- a/code/modules/events/sentience.dm +++ b/code/modules/events/sentience.dm @@ -48,7 +48,7 @@ spawned_animals++ - SA.key = SG.key + SG.transfer_ckey(SA, FALSE) SA.grant_all_languages(TRUE) diff --git a/code/modules/events/wizard/imposter.dm b/code/modules/events/wizard/imposter.dm index 1c8ef95baa..29704168e9 100644 --- a/code/modules/events/wizard/imposter.dm +++ b/code/modules/events/wizard/imposter.dm @@ -23,7 +23,7 @@ I.name = I.dna.real_name I.updateappearance(mutcolor_update=1) I.domutcheck() - I.key = C.key + C.transfer_ckey(I, FALSE) var/datum/antagonist/wizard/master = M.has_antag_datum(/datum/antagonist/wizard) if(!master.wiz_team) master.create_wiz_team() diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 1cd32d43ac..f0b7c18b28 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -1,623 +1,623 @@ -#define LINKIFY_READY(string, value) "[string]" - -/mob/dead/new_player - var/ready = 0 - var/spawning = 0//Referenced when you want to delete the new_player later on in the code. - - flags_1 = NONE - - invisibility = INVISIBILITY_ABSTRACT - - density = FALSE - stat = DEAD - canmove = FALSE - - anchored = TRUE // don't get pushed around - - var/mob/living/new_character //for instant transfer once the round is set up - - //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles - var/ineligible_for_roles = FALSE - -/mob/dead/new_player/Initialize() - if(client && SSticker.state == GAME_STATE_STARTUP) - var/obj/screen/splash/S = new(client, TRUE, TRUE) - S.Fade(TRUE) - - if(length(GLOB.newplayer_start)) - forceMove(pick(GLOB.newplayer_start)) - else - forceMove(locate(1,1,1)) - - ComponentInitialize() - - . = ..() - -/mob/dead/new_player/prepare_huds() - return - -/mob/dead/new_player/proc/new_player_panel() - var/output = "

Welcome, [client ? client.prefs.real_name : "Unknown User"]

" - output += "

Setup Character

" - - if(SSticker.current_state <= GAME_STATE_PREGAME) - switch(ready) - if(PLAYER_NOT_READY) - output += "

\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

" - if(PLAYER_READY_TO_PLAY) - output += "

\[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

" - if(PLAYER_READY_TO_OBSERVE) - output += "

\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]

" - else - output += "

View the Crew Manifest

" - output += "

Join Game!

" - output += "

[LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

" - - if(!IsGuestKey(src.key)) - if (SSdbcore.Connect()) - var/isadmin = 0 - if(src.client && src.client.holder) - isadmin = 1 - var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")") - var/rs = REF(src) - if(query_get_new_polls.Execute()) - var/newpoll = 0 - if(query_get_new_polls.NextRow()) - newpoll = 1 - - if(newpoll) - output += "

Show Player Polls (NEW!)

" - else - output += "

Show Player Polls

" - qdel(query_get_new_polls) - if(QDELETED(src)) - return - - output += "
" - - //src << browse(output,"window=playersetup;size=210x240;can_close=0") - var/datum/browser/popup = new(src, "playersetup", "
New Player Options
", 250, 265) - popup.set_window_options("can_close=0") - popup.set_content(output) - popup.open(FALSE) - -/mob/dead/new_player/Topic(href, href_list[]) - if(src != usr) - return 0 - - if(!client) - return 0 - - //Determines Relevent Population Cap - var/relevant_cap - var/hpc = CONFIG_GET(number/hard_popcap) - var/epc = CONFIG_GET(number/extreme_popcap) - if(hpc && epc) - relevant_cap = min(hpc, epc) - else - relevant_cap = max(hpc, epc) - - if(href_list["show_preferences"]) - client.prefs.ShowChoices(src) - return 1 - - if(href_list["ready"]) - var/tready = text2num(href_list["ready"]) - //Avoid updating ready if we're after PREGAME (they should use latejoin instead) - //This is likely not an actual issue but I don't have time to prove that this - //no longer is required - if(SSticker.current_state <= GAME_STATE_PREGAME) - ready = tready - //if it's post initialisation and they're trying to observe we do the needful - if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE) - ready = tready - make_me_an_observer() - return - - if(href_list["refresh"]) - src << browse(null, "window=playersetup") //closes the player setup window - new_player_panel() - - if(href_list["late_join"]) - if(!SSticker || !SSticker.IsRoundInProgress()) - to_chat(usr, "The round is either not ready, or has already finished...") - return - - if(href_list["late_join"] == "override") - LateChoices() - return - - if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) - to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)]") - - var/queue_position = SSticker.queued_players.Find(usr) - if(queue_position == 1) - to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up.") - else if(queue_position) - to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game.") - else - SSticker.queued_players += usr - to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].") - return - LateChoices() - - if(href_list["manifest"]) - ViewManifest() - - if(href_list["SelectedJob"]) - - if(!GLOB.enter_allowed) - to_chat(usr, "There is an administrative lock on entering the game!") - return - - if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) - if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) - to_chat(usr, "Server is full.") - return - - AttemptLateSpawn(href_list["SelectedJob"]) - return - - if(href_list["JoinAsGhostRole"]) - if(!GLOB.enter_allowed) - to_chat(usr, " There is an administrative lock on entering the game!") - - if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) - if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) - to_chat(usr, "Server is full.") - return - - var/obj/effect/mob_spawn/MS = pick(GLOB.mob_spawners[href_list["JoinAsGhostRole"]]) - if(MS.attack_ghost(src, latejoinercalling = TRUE)) - SSticker.queued_players -= src - SSticker.queue_delay = 4 - qdel(src) - - if(!ready && href_list["preference"]) - if(client) - client.prefs.process_link(src, href_list) - else if(!href_list["late_join"]) - new_player_panel() - - if(href_list["showpoll"]) - handle_player_polling() - return - - if(href_list["pollid"]) - var/pollid = href_list["pollid"] - if(istext(pollid)) - pollid = text2num(pollid) - if(isnum(pollid) && ISINTEGER(pollid)) - src.poll_player(pollid) - return - - if(href_list["votepollid"] && href_list["votetype"]) - var/pollid = text2num(href_list["votepollid"]) - var/votetype = href_list["votetype"] - //lets take data from the user to decide what kind of poll this is, without validating it - //what could go wrong - switch(votetype) - if(POLLTYPE_OPTION) - var/optionid = text2num(href_list["voteoptionid"]) - if(vote_on_poll(pollid, optionid)) - to_chat(usr, "Vote successful.") - else - to_chat(usr, "Vote failed, please try again or contact an administrator.") - if(POLLTYPE_TEXT) - var/replytext = href_list["replytext"] - if(log_text_poll_reply(pollid, replytext)) - to_chat(usr, "Feedback logging successful.") - else - to_chat(usr, "Feedback logging failed, please try again or contact an administrator.") - if(POLLTYPE_RATING) - var/id_min = text2num(href_list["minid"]) - var/id_max = text2num(href_list["maxid"]) - - if( (id_max - id_min) > 100 ) //Basic exploit prevention - //(protip, this stops no exploits) - to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") - return - - for(var/optionid = id_min; optionid <= id_max; optionid++) - if(!isnull(href_list["o[optionid]"])) //Test if this optionid was replied to - var/rating - if(href_list["o[optionid]"] == "abstain") - rating = null - else - rating = text2num(href_list["o[optionid]"]) - if(!isnum(rating) || !ISINTEGER(rating)) - return - - if(!vote_on_numval_poll(pollid, optionid, rating)) - to_chat(usr, "Vote failed, please try again or contact an administrator.") - return - to_chat(usr, "Vote successful.") - if(POLLTYPE_MULTI) - var/id_min = text2num(href_list["minoptionid"]) - var/id_max = text2num(href_list["maxoptionid"]) - - if( (id_max - id_min) > 100 ) //Basic exploit prevention - to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") - return - - for(var/optionid = id_min; optionid <= id_max; optionid++) - if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected - var/i = vote_on_multi_poll(pollid, optionid) - switch(i) - if(0) - continue - if(1) - to_chat(usr, "Vote failed, please try again or contact an administrator.") - return - if(2) - to_chat(usr, "Maximum replies reached.") - break - to_chat(usr, "Vote successful.") - if(POLLTYPE_IRV) - if (!href_list["IRVdata"]) - to_chat(src, "No ordering data found. Please try again or contact an administrator.") - return - var/list/votelist = splittext(href_list["IRVdata"], ",") - if (!vote_on_irv_poll(pollid, votelist)) - to_chat(src, "Vote failed, please try again or contact an administrator.") - return - to_chat(src, "Vote successful.") - -//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT) -/mob/dead/new_player/proc/make_me_an_observer() - if(QDELETED(src) || !src.client) - ready = PLAYER_NOT_READY - return FALSE - - var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No") - - if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes") - ready = PLAYER_NOT_READY - src << browse(null, "window=playersetup") //closes the player setup window - new_player_panel() - return FALSE - - var/mob/dead/observer/observer = new() - spawning = TRUE - - observer.started_as_observer = TRUE - close_spawn_windows() - var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list - to_chat(src, "Now teleporting.") - if (O) - observer.forceMove(O.loc) - else - to_chat(src, "Teleporting failed. Ahelp an admin please") - stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised") - observer.key = key - observer.client = client - observer.set_ghost_appearance() - if(observer.client && observer.client.prefs) - observer.real_name = observer.client.prefs.real_name - observer.name = observer.real_name - observer.update_icon() - observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) - QDEL_NULL(mind) - qdel(src) - return TRUE - -/proc/get_job_unavailable_error_message(retval, jobtitle) - switch(retval) - if(JOB_AVAILABLE) - return "[jobtitle] is available." - if(JOB_UNAVAILABLE_GENERIC) - return "[jobtitle] is unavailable." - if(JOB_UNAVAILABLE_BANNED) - return "You are currently banned from [jobtitle]." - if(JOB_UNAVAILABLE_PLAYTIME) - return "You do not have enough relevant playtime for [jobtitle]." - if(JOB_UNAVAILABLE_ACCOUNTAGE) - return "Your account is not old enough for [jobtitle]." - if(JOB_UNAVAILABLE_SLOTFULL) - return "[jobtitle] is already filled to capacity." - return "Error: Unknown job availability." - -/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) - var/datum/job/job = SSjob.GetJob(rank) - if(!job) - return JOB_UNAVAILABLE_GENERIC - if((job.current_positions >= job.total_positions) && job.total_positions != -1) - if(job.title == "Assistant") - if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants - return JOB_AVAILABLE - for(var/datum/job/J in SSjob.occupations) - if(J && J.current_positions < J.total_positions && J.title != job.title) - return JOB_UNAVAILABLE_SLOTFULL - else - return JOB_UNAVAILABLE_SLOTFULL - if(jobban_isbanned(src,rank)) - return JOB_UNAVAILABLE_BANNED - if(QDELETED(src)) - return JOB_UNAVAILABLE_GENERIC - if(!job.player_old_enough(client)) - return JOB_UNAVAILABLE_ACCOUNTAGE - if(job.required_playtime_remaining(client)) - return JOB_UNAVAILABLE_PLAYTIME - if(latejoin && !job.special_check_latejoin(client)) - return JOB_UNAVAILABLE_GENERIC - return JOB_AVAILABLE - -/mob/dead/new_player/proc/AttemptLateSpawn(rank) - var/error = IsJobUnavailable(rank) - if(error != JOB_AVAILABLE) - alert(src, get_job_unavailable_error_message(error, rank)) - return FALSE - - if(SSticker.late_join_disabled) - alert(src, "An administrator has disabled late join spawning.") - return FALSE - - var/arrivals_docked = TRUE - if(SSshuttle.arrivals) - close_spawn_windows() //In case we get held up - if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) - src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") - return FALSE - - if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) - SSshuttle.arrivals.RequireUndocked(src) - arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL - - //Remove the player from the join queue if he was in one and reset the timer - SSticker.queued_players -= src - SSticker.queue_delay = 4 - - SSjob.AssignRole(src, rank, 1) - - var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind - var/equip = SSjob.EquipRank(character, rank, TRUE) - if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. - character = equip - - var/datum/job/job = SSjob.GetJob(rank) - - if(job && !job.override_latejoin_spawn(character)) - SSjob.SendToLateJoin(character) - if(!arrivals_docked) - var/obj/screen/splash/Spl = new(character.client, TRUE) - Spl.Fade(TRUE) - character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) - - character.update_parallax_teleport() - - SSticker.minds += character.mind - - var/mob/living/carbon/human/humanc - if(ishuman(character)) - humanc = character //Let's retypecast the var to be human, - - if(humanc) //These procs all expect humans - GLOB.data_core.manifest_inject(humanc) - if(SSshuttle.arrivals) - SSshuttle.arrivals.QueueAnnounce(humanc, rank) - else - AnnounceArrival(humanc, rank) - AddEmploymentContract(humanc) - if(GLOB.highlander) - to_chat(humanc, "THERE CAN BE ONLY ONE!!!") - humanc.make_scottish() - - if(GLOB.summon_guns_triggered) - give_guns(humanc) - if(GLOB.summon_magic_triggered) - give_magic(humanc) - - GLOB.joined_player_list += character.ckey - GLOB.latejoiners += character - - if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_RECALL, SHUTTLE_IDLE) - SSticker.mode.make_antag_chance(humanc) - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) - SSticker.mode.make_antag_chance(humanc) - - if(humanc && CONFIG_GET(flag/roundstart_traits)) - SSquirks.AssignQuirks(humanc, humanc.client, TRUE, FALSE, job, FALSE) - - log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) - -/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) - //TODO: figure out a way to exclude wizards/nukeops/demons from this. - for(var/C in GLOB.employmentCabinets) - var/obj/structure/filingcabinet/employment/employmentCabinet = C - if(!employmentCabinet.virgin) - employmentCabinet.addFile(employee) - - -/mob/dead/new_player/proc/LateChoices() - - var/level = "green" - switch(GLOB.security_level) - if(SEC_LEVEL_GREEN) - level = "green" - if(SEC_LEVEL_BLUE) - level = "blue" - if(SEC_LEVEL_AMBER) - level = "amber" - if(SEC_LEVEL_RED) - level = "red" - if(SEC_LEVEL_DELTA) - level = "delta" - - var/dat = "
Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
Alert Level: [capitalize(level)]
" - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_ESCAPE) - dat += "
The station has been evacuated.

" - if(SHUTTLE_CALL) - if(!SSshuttle.canRecall()) - dat += "
The station is currently undergoing evacuation procedures.

" - for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) - if(prioritized_job.current_positions >= prioritized_job.total_positions) - SSjob.prioritized_jobs -= prioritized_job - dat += "
" - var/column_counter = 0 - var/free_space = 0 - for(var/list/category in list(GLOB.command_positions) + list(GLOB.supply_positions) + list(GLOB.engineering_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions)) - var/cat_color = "fff" //random default - if(SSjob.name_occupations && SSjob.name_occupations[category[1]]) - cat_color = SSjob.name_occupations[category[1]].selection_color //use the color of the first job in the category (the department head) as the category color - else - cat_color = SSjob.occupations[category[1]].selection_color - dat += "
" - dat += "[SSjob.name_occupations[category[1]].exp_type_department]" - - var/list/dept_dat = list() - for(var/job in category) - var/datum/job/job_datum = SSjob.name_occupations[job] - if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE) - var/command_bold = "" - if(job in GLOB.command_positions) - command_bold = " command" - if(job_datum in SSjob.prioritized_jobs) - dept_dat += "[job_datum.title] ([job_datum.current_positions])" - else - dept_dat += "[job_datum.title] ([job_datum.current_positions])" - if(!dept_dat.len) - dept_dat += "No positions open." - dat += jointext(dept_dat, "") - dat += "

" - column_counter++ - if(free_space <=4) - free_space++ - if(column_counter > 0 && (column_counter % 3 == 0)) - dat += "
" - if(free_space >= 5 && (free_space % 5 == 0) && (column_counter % 3 != 0)) - free_space = 0 - column_counter = 0 - dat += "" - - dat += "
" - - var/available_ghosts = 0 - for(var/spawner in GLOB.mob_spawners) - if(!LAZYLEN(spawner)) - continue - var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) - if(!istype(S) || !S.can_latejoin()) - continue - available_ghosts++ - break - - if(!available_ghosts) - dat += "
There are currently no open ghost spawners.
" - else - var/list/categorizedJobs = list("Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff")) - for(var/spawner in GLOB.mob_spawners) - if(!LAZYLEN(spawner)) - continue - var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) - if(!istype(S) || !S.can_latejoin()) - continue - categorizedJobs["Ghost Role"]["jobs"] += spawner - - dat += "
" - for(var/jobcat in categorizedJobs) - if(!length(categorizedJobs[jobcat]["jobs"])) - continue - var/color = categorizedJobs[jobcat]["color"] - dat += "
" - dat += "[jobcat]" - for(var/spawner in categorizedJobs[jobcat]["jobs"]) - dat += "[spawner]" - - dat += "

" - dat += "
" - dat += "" - - var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600) - popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') - popup.set_content(jointext(dat, "")) - popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc - -/mob/dead/new_player/proc/create_character(transfer_after) - spawning = 1 - close_spawn_windows() - - var/mob/living/carbon/human/H = new(loc) - - var/frn = CONFIG_GET(flag/force_random_names) - if(!frn) - frn = jobban_isbanned(src, "appearance") - if(QDELETED(src)) - return - if(frn) - client.prefs.random_character() - client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) - client.prefs.copy_to(H) - H.dna.update_dna_identity() - if(mind) - if(transfer_after) - mind.late_joiner = TRUE - mind.active = 0 //we wish to transfer the key manually - mind.transfer_to(H) //won't transfer key since the mind is not active - - H.name = real_name - - . = H - new_character = . - if(transfer_after) - transfer_character() - -/mob/dead/new_player/proc/transfer_character() - . = new_character - if(.) - new_character.key = key //Manually transfer the key to log them in - new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC) - new_character = null - qdel(src) - -/mob/dead/new_player/proc/ViewManifest() - var/dat = "" - dat += "

Crew Manifest

" - dat += GLOB.data_core.get_manifest(OOC = 1) - - src << browse(dat, "window=manifest;size=387x420;can_close=1") - -/mob/dead/new_player/Move() - return 0 - - -/mob/dead/new_player/proc/close_spawn_windows() - - src << browse(null, "window=latechoices") //closes late choices window - src << browse(null, "window=playersetup") //closes the player setup window - src << browse(null, "window=preferences") //closes job selection - src << browse(null, "window=mob_occupation") - src << browse(null, "window=latechoices") //closes late job selection - -/* Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. - A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled - Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" - Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role - This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong -*/ - -/mob/dead/new_player/proc/check_preferences() - if(!client) - return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. - if(client.prefs.joblessrole != RETURNTOLOBBY) - return TRUE - // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. - var/has_antags = FALSE - if(client.prefs.be_special.len > 0) - has_antags = TRUE - if(client.prefs.job_preferences.len == 0) - if(!ineligible_for_roles) - to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") - ineligible_for_roles = TRUE - ready = PLAYER_NOT_READY - if(has_antags) - log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") - message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") - - return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well - return TRUE +#define LINKIFY_READY(string, value) "[string]" + +/mob/dead/new_player + var/ready = 0 + var/spawning = 0//Referenced when you want to delete the new_player later on in the code. + + flags_1 = NONE + + invisibility = INVISIBILITY_ABSTRACT + + density = FALSE + stat = DEAD + canmove = FALSE + + anchored = TRUE // don't get pushed around + + var/mob/living/new_character //for instant transfer once the round is set up + + //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles + var/ineligible_for_roles = FALSE + +/mob/dead/new_player/Initialize() + if(client && SSticker.state == GAME_STATE_STARTUP) + var/obj/screen/splash/S = new(client, TRUE, TRUE) + S.Fade(TRUE) + + if(length(GLOB.newplayer_start)) + forceMove(pick(GLOB.newplayer_start)) + else + forceMove(locate(1,1,1)) + + ComponentInitialize() + + . = ..() + +/mob/dead/new_player/prepare_huds() + return + +/mob/dead/new_player/proc/new_player_panel() + var/output = "

Welcome, [client ? client.prefs.real_name : "Unknown User"]

" + output += "

Setup Character

" + + if(SSticker.current_state <= GAME_STATE_PREGAME) + switch(ready) + if(PLAYER_NOT_READY) + output += "

\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

" + if(PLAYER_READY_TO_PLAY) + output += "

\[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

" + if(PLAYER_READY_TO_OBSERVE) + output += "

\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]

" + else + output += "

View the Crew Manifest

" + output += "

Join Game!

" + output += "

[LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

" + + if(!IsGuestKey(src.key)) + if (SSdbcore.Connect()) + var/isadmin = 0 + if(src.client && src.client.holder) + isadmin = 1 + var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")") + var/rs = REF(src) + if(query_get_new_polls.Execute()) + var/newpoll = 0 + if(query_get_new_polls.NextRow()) + newpoll = 1 + + if(newpoll) + output += "

Show Player Polls (NEW!)

" + else + output += "

Show Player Polls

" + qdel(query_get_new_polls) + if(QDELETED(src)) + return + + output += "
" + + //src << browse(output,"window=playersetup;size=210x240;can_close=0") + var/datum/browser/popup = new(src, "playersetup", "
New Player Options
", 250, 265) + popup.set_window_options("can_close=0") + popup.set_content(output) + popup.open(FALSE) + +/mob/dead/new_player/Topic(href, href_list[]) + if(src != usr) + return 0 + + if(!client) + return 0 + + //Determines Relevent Population Cap + var/relevant_cap + var/hpc = CONFIG_GET(number/hard_popcap) + var/epc = CONFIG_GET(number/extreme_popcap) + if(hpc && epc) + relevant_cap = min(hpc, epc) + else + relevant_cap = max(hpc, epc) + + if(href_list["show_preferences"]) + client.prefs.ShowChoices(src) + return 1 + + if(href_list["ready"]) + var/tready = text2num(href_list["ready"]) + //Avoid updating ready if we're after PREGAME (they should use latejoin instead) + //This is likely not an actual issue but I don't have time to prove that this + //no longer is required + if(SSticker.current_state <= GAME_STATE_PREGAME) + ready = tready + //if it's post initialisation and they're trying to observe we do the needful + if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE) + ready = tready + make_me_an_observer() + return + + if(href_list["refresh"]) + src << browse(null, "window=playersetup") //closes the player setup window + new_player_panel() + + if(href_list["late_join"]) + if(!SSticker || !SSticker.IsRoundInProgress()) + to_chat(usr, "The round is either not ready, or has already finished...") + return + + if(href_list["late_join"] == "override") + LateChoices() + return + + if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) + to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)]") + + var/queue_position = SSticker.queued_players.Find(usr) + if(queue_position == 1) + to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up.") + else if(queue_position) + to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game.") + else + SSticker.queued_players += usr + to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].") + return + LateChoices() + + if(href_list["manifest"]) + ViewManifest() + + if(href_list["SelectedJob"]) + + if(!GLOB.enter_allowed) + to_chat(usr, "There is an administrative lock on entering the game!") + return + + if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) + if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) + to_chat(usr, "Server is full.") + return + + AttemptLateSpawn(href_list["SelectedJob"]) + return + + if(href_list["JoinAsGhostRole"]) + if(!GLOB.enter_allowed) + to_chat(usr, " There is an administrative lock on entering the game!") + + if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) + if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) + to_chat(usr, "Server is full.") + return + + var/obj/effect/mob_spawn/MS = pick(GLOB.mob_spawners[href_list["JoinAsGhostRole"]]) + if(MS.attack_ghost(src, latejoinercalling = TRUE)) + SSticker.queued_players -= src + SSticker.queue_delay = 4 + qdel(src) + + if(!ready && href_list["preference"]) + if(client) + client.prefs.process_link(src, href_list) + else if(!href_list["late_join"]) + new_player_panel() + + if(href_list["showpoll"]) + handle_player_polling() + return + + if(href_list["pollid"]) + var/pollid = href_list["pollid"] + if(istext(pollid)) + pollid = text2num(pollid) + if(isnum(pollid) && ISINTEGER(pollid)) + src.poll_player(pollid) + return + + if(href_list["votepollid"] && href_list["votetype"]) + var/pollid = text2num(href_list["votepollid"]) + var/votetype = href_list["votetype"] + //lets take data from the user to decide what kind of poll this is, without validating it + //what could go wrong + switch(votetype) + if(POLLTYPE_OPTION) + var/optionid = text2num(href_list["voteoptionid"]) + if(vote_on_poll(pollid, optionid)) + to_chat(usr, "Vote successful.") + else + to_chat(usr, "Vote failed, please try again or contact an administrator.") + if(POLLTYPE_TEXT) + var/replytext = href_list["replytext"] + if(log_text_poll_reply(pollid, replytext)) + to_chat(usr, "Feedback logging successful.") + else + to_chat(usr, "Feedback logging failed, please try again or contact an administrator.") + if(POLLTYPE_RATING) + var/id_min = text2num(href_list["minid"]) + var/id_max = text2num(href_list["maxid"]) + + if( (id_max - id_min) > 100 ) //Basic exploit prevention + //(protip, this stops no exploits) + to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") + return + + for(var/optionid = id_min; optionid <= id_max; optionid++) + if(!isnull(href_list["o[optionid]"])) //Test if this optionid was replied to + var/rating + if(href_list["o[optionid]"] == "abstain") + rating = null + else + rating = text2num(href_list["o[optionid]"]) + if(!isnum(rating) || !ISINTEGER(rating)) + return + + if(!vote_on_numval_poll(pollid, optionid, rating)) + to_chat(usr, "Vote failed, please try again or contact an administrator.") + return + to_chat(usr, "Vote successful.") + if(POLLTYPE_MULTI) + var/id_min = text2num(href_list["minoptionid"]) + var/id_max = text2num(href_list["maxoptionid"]) + + if( (id_max - id_min) > 100 ) //Basic exploit prevention + to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") + return + + for(var/optionid = id_min; optionid <= id_max; optionid++) + if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected + var/i = vote_on_multi_poll(pollid, optionid) + switch(i) + if(0) + continue + if(1) + to_chat(usr, "Vote failed, please try again or contact an administrator.") + return + if(2) + to_chat(usr, "Maximum replies reached.") + break + to_chat(usr, "Vote successful.") + if(POLLTYPE_IRV) + if (!href_list["IRVdata"]) + to_chat(src, "No ordering data found. Please try again or contact an administrator.") + return + var/list/votelist = splittext(href_list["IRVdata"], ",") + if (!vote_on_irv_poll(pollid, votelist)) + to_chat(src, "Vote failed, please try again or contact an administrator.") + return + to_chat(src, "Vote successful.") + +//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT) +/mob/dead/new_player/proc/make_me_an_observer() + if(QDELETED(src) || !src.client) + ready = PLAYER_NOT_READY + return FALSE + + var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No") + + if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes") + ready = PLAYER_NOT_READY + src << browse(null, "window=playersetup") //closes the player setup window + new_player_panel() + return FALSE + + var/mob/dead/observer/observer = new() + spawning = TRUE + + observer.started_as_observer = TRUE + close_spawn_windows() + var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list + to_chat(src, "Now teleporting.") + if (O) + observer.forceMove(O.loc) + else + to_chat(src, "Teleporting failed. Ahelp an admin please") + stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised") + transfer_ckey(observer, FALSE) + observer.client = client + observer.set_ghost_appearance() + if(observer.client && observer.client.prefs) + observer.real_name = observer.client.prefs.real_name + observer.name = observer.real_name + observer.update_icon() + observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) + QDEL_NULL(mind) + qdel(src) + return TRUE + +/proc/get_job_unavailable_error_message(retval, jobtitle) + switch(retval) + if(JOB_AVAILABLE) + return "[jobtitle] is available." + if(JOB_UNAVAILABLE_GENERIC) + return "[jobtitle] is unavailable." + if(JOB_UNAVAILABLE_BANNED) + return "You are currently banned from [jobtitle]." + if(JOB_UNAVAILABLE_PLAYTIME) + return "You do not have enough relevant playtime for [jobtitle]." + if(JOB_UNAVAILABLE_ACCOUNTAGE) + return "Your account is not old enough for [jobtitle]." + if(JOB_UNAVAILABLE_SLOTFULL) + return "[jobtitle] is already filled to capacity." + return "Error: Unknown job availability." + +/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) + var/datum/job/job = SSjob.GetJob(rank) + if(!job) + return JOB_UNAVAILABLE_GENERIC + if((job.current_positions >= job.total_positions) && job.total_positions != -1) + if(job.title == "Assistant") + if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants + return JOB_AVAILABLE + for(var/datum/job/J in SSjob.occupations) + if(J && J.current_positions < J.total_positions && J.title != job.title) + return JOB_UNAVAILABLE_SLOTFULL + else + return JOB_UNAVAILABLE_SLOTFULL + if(jobban_isbanned(src,rank)) + return JOB_UNAVAILABLE_BANNED + if(QDELETED(src)) + return JOB_UNAVAILABLE_GENERIC + if(!job.player_old_enough(client)) + return JOB_UNAVAILABLE_ACCOUNTAGE + if(job.required_playtime_remaining(client)) + return JOB_UNAVAILABLE_PLAYTIME + if(latejoin && !job.special_check_latejoin(client)) + return JOB_UNAVAILABLE_GENERIC + return JOB_AVAILABLE + +/mob/dead/new_player/proc/AttemptLateSpawn(rank) + var/error = IsJobUnavailable(rank) + if(error != JOB_AVAILABLE) + alert(src, get_job_unavailable_error_message(error, rank)) + return FALSE + + if(SSticker.late_join_disabled) + alert(src, "An administrator has disabled late join spawning.") + return FALSE + + var/arrivals_docked = TRUE + if(SSshuttle.arrivals) + close_spawn_windows() //In case we get held up + if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) + src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") + return FALSE + + if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) + SSshuttle.arrivals.RequireUndocked(src) + arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL + + //Remove the player from the join queue if he was in one and reset the timer + SSticker.queued_players -= src + SSticker.queue_delay = 4 + + SSjob.AssignRole(src, rank, 1) + + var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind + var/equip = SSjob.EquipRank(character, rank, TRUE) + if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. + character = equip + + var/datum/job/job = SSjob.GetJob(rank) + + if(job && !job.override_latejoin_spawn(character)) + SSjob.SendToLateJoin(character) + if(!arrivals_docked) + var/obj/screen/splash/Spl = new(character.client, TRUE) + Spl.Fade(TRUE) + character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) + + character.update_parallax_teleport() + + SSticker.minds += character.mind + + var/mob/living/carbon/human/humanc + if(ishuman(character)) + humanc = character //Let's retypecast the var to be human, + + if(humanc) //These procs all expect humans + GLOB.data_core.manifest_inject(humanc) + if(SSshuttle.arrivals) + SSshuttle.arrivals.QueueAnnounce(humanc, rank) + else + AnnounceArrival(humanc, rank) + AddEmploymentContract(humanc) + if(GLOB.highlander) + to_chat(humanc, "THERE CAN BE ONLY ONE!!!") + humanc.make_scottish() + + if(GLOB.summon_guns_triggered) + give_guns(humanc) + if(GLOB.summon_magic_triggered) + give_magic(humanc) + + GLOB.joined_player_list += character.ckey + GLOB.latejoiners += character + + if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_RECALL, SHUTTLE_IDLE) + SSticker.mode.make_antag_chance(humanc) + if(SHUTTLE_CALL) + if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) + SSticker.mode.make_antag_chance(humanc) + + if(humanc && CONFIG_GET(flag/roundstart_traits)) + SSquirks.AssignQuirks(humanc, humanc.client, TRUE, FALSE, job, FALSE) + + log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) + +/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) + //TODO: figure out a way to exclude wizards/nukeops/demons from this. + for(var/C in GLOB.employmentCabinets) + var/obj/structure/filingcabinet/employment/employmentCabinet = C + if(!employmentCabinet.virgin) + employmentCabinet.addFile(employee) + + +/mob/dead/new_player/proc/LateChoices() + + var/level = "green" + switch(GLOB.security_level) + if(SEC_LEVEL_GREEN) + level = "green" + if(SEC_LEVEL_BLUE) + level = "blue" + if(SEC_LEVEL_AMBER) + level = "amber" + if(SEC_LEVEL_RED) + level = "red" + if(SEC_LEVEL_DELTA) + level = "delta" + + var/dat = "
Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
Alert Level: [capitalize(level)]
" + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_ESCAPE) + dat += "
The station has been evacuated.

" + if(SHUTTLE_CALL) + if(!SSshuttle.canRecall()) + dat += "
The station is currently undergoing evacuation procedures.

" + for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) + if(prioritized_job.current_positions >= prioritized_job.total_positions) + SSjob.prioritized_jobs -= prioritized_job + dat += "
" + var/column_counter = 0 + var/free_space = 0 + for(var/list/category in list(GLOB.command_positions) + list(GLOB.supply_positions) + list(GLOB.engineering_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions)) + var/cat_color = "fff" //random default + if(SSjob.name_occupations && SSjob.name_occupations[category[1]]) + cat_color = SSjob.name_occupations[category[1]].selection_color //use the color of the first job in the category (the department head) as the category color + else + cat_color = SSjob.occupations[category[1]].selection_color + dat += "
" + dat += "[SSjob.name_occupations[category[1]].exp_type_department]" + + var/list/dept_dat = list() + for(var/job in category) + var/datum/job/job_datum = SSjob.name_occupations[job] + if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE) + var/command_bold = "" + if(job in GLOB.command_positions) + command_bold = " command" + if(job_datum in SSjob.prioritized_jobs) + dept_dat += "[job_datum.title] ([job_datum.current_positions])" + else + dept_dat += "[job_datum.title] ([job_datum.current_positions])" + if(!dept_dat.len) + dept_dat += "No positions open." + dat += jointext(dept_dat, "") + dat += "

" + column_counter++ + if(free_space <=4) + free_space++ + if(column_counter > 0 && (column_counter % 3 == 0)) + dat += "
" + if(free_space >= 5 && (free_space % 5 == 0) && (column_counter % 3 != 0)) + free_space = 0 + column_counter = 0 + dat += "" + + dat += "
" + + var/available_ghosts = 0 + for(var/spawner in GLOB.mob_spawners) + if(!LAZYLEN(spawner)) + continue + var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) + if(!istype(S) || !S.can_latejoin()) + continue + available_ghosts++ + break + + if(!available_ghosts) + dat += "
There are currently no open ghost spawners.
" + else + var/list/categorizedJobs = list("Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff")) + for(var/spawner in GLOB.mob_spawners) + if(!LAZYLEN(spawner)) + continue + var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) + if(!istype(S) || !S.can_latejoin()) + continue + categorizedJobs["Ghost Role"]["jobs"] += spawner + + dat += "
" + for(var/jobcat in categorizedJobs) + if(!length(categorizedJobs[jobcat]["jobs"])) + continue + var/color = categorizedJobs[jobcat]["color"] + dat += "
" + dat += "[jobcat]" + for(var/spawner in categorizedJobs[jobcat]["jobs"]) + dat += "[spawner]" + + dat += "

" + dat += "
" + dat += "" + + var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600) + popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') + popup.set_content(jointext(dat, "")) + popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc + +/mob/dead/new_player/proc/create_character(transfer_after) + spawning = 1 + close_spawn_windows() + + var/mob/living/carbon/human/H = new(loc) + + var/frn = CONFIG_GET(flag/force_random_names) + if(!frn) + frn = jobban_isbanned(src, "appearance") + if(QDELETED(src)) + return + if(frn) + client.prefs.random_character() + client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) + client.prefs.copy_to(H) + H.dna.update_dna_identity() + if(mind) + if(transfer_after) + mind.late_joiner = TRUE + mind.active = 0 //we wish to transfer the key manually + mind.transfer_to(H) //won't transfer key since the mind is not active + + H.name = real_name + + . = H + new_character = . + if(transfer_after) + transfer_character() + +/mob/dead/new_player/proc/transfer_character() + . = new_character + if(.) + new_character.key = key //Manually transfer the key to log them in + new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC) + new_character = null + qdel(src) + +/mob/dead/new_player/proc/ViewManifest() + var/dat = "" + dat += "

Crew Manifest

" + dat += GLOB.data_core.get_manifest(OOC = 1) + + src << browse(dat, "window=manifest;size=387x420;can_close=1") + +/mob/dead/new_player/Move() + return 0 + + +/mob/dead/new_player/proc/close_spawn_windows() + + src << browse(null, "window=latechoices") //closes late choices window + src << browse(null, "window=playersetup") //closes the player setup window + src << browse(null, "window=preferences") //closes job selection + src << browse(null, "window=mob_occupation") + src << browse(null, "window=latechoices") //closes late job selection + +/* Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. + A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled + Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" + Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role + This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong +*/ + +/mob/dead/new_player/proc/check_preferences() + if(!client) + return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. + if(client.prefs.joblessrole != RETURNTOLOBBY) + return TRUE + // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. + var/has_antags = FALSE + if(client.prefs.be_special.len > 0) + has_antags = TRUE + if(client.prefs.job_preferences.len == 0) + if(!ineligible_for_roles) + to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") + ineligible_for_roles = TRUE + ready = PLAYER_NOT_READY + if(has_antags) + log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") + message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") + + return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well + return TRUE diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index fefa032e4f..d599d55886 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -260,16 +260,16 @@ Transfer_mind is there to check if mob is being deleted/not going to have a body Works together with spawning an observer, noted above. */ -/mob/proc/ghostize(can_reenter_corpse = 1) - if(key) - if(!cmptext(copytext(key,1,2),"@")) // Skip aghosts. - stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now - var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc. - SStgui.on_transfer(src, ghost) // Transfer NanoUIs. - ghost.can_reenter_corpse = can_reenter_corpse - ghost.can_reenter_round = (can_reenter_corpse && !suiciding) - ghost.key = key - return ghost +/mob/proc/ghostize(can_reenter_corpse = TRUE, special = FALSE) + if(!key || cmptext(copytext(key,1,2),"@") || (!special && SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse, special) & COMPONENT_BLOCK_GHOSTING)) + return //mob has no key, is an aghost or some component hijacked. + stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now + var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc. + SStgui.on_transfer(src, ghost) // Transfer NanoUIs. + ghost.can_reenter_corpse = can_reenter_corpse + ghost.can_reenter_round = (can_reenter_corpse && !suiciding) + transfer_ckey(ghost, FALSE) + return ghost /* This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues. @@ -280,6 +280,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set name = "Ghost" set desc = "Relinquish your life and enter the land of the dead." + if(SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, (stat == DEAD) ? TRUE : FALSE, FALSE) & COMPONENT_BLOCK_GHOSTING) + return + // CITADEL EDIT if(istype(loc, /obj/machinery/cryopod)) var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you won't be able to re-enter this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body") @@ -306,6 +309,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set name = "Ghost" set desc = "Relinquish your life and enter the land of the dead." + if(SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, FALSE, FALSE) & COMPONENT_BLOCK_GHOSTING) + return + var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you won't be able to re-enter this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body") if(response != "Ghost") return @@ -348,7 +354,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return client.change_view(CONFIG_GET(string/default_view)) SStgui.on_transfer(src, mind.current) // Transfer NanoUIs. - mind.current.key = key + transfer_ckey(mind.current, FALSE) return 1 /mob/dead/observer/proc/notify_cloning(var/message, var/sound, var/atom/source, flashwindow = TRUE) @@ -628,7 +634,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp to_chat(src, "Someone has taken this body while you were choosing!") return 0 - target.key = key + transfer_ckey(target, FALSE) target.faction = list("neutral") return 1 diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 17f81981d1..41a8944015 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -42,7 +42,7 @@ if(brainmob.mind) brainmob.mind.transfer_to(C) else - C.key = brainmob.key + brainmob.transfer_ckey(C) QDEL_NULL(brainmob) diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index 9706fde782..04a2e56857 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -87,7 +87,7 @@ var/atom/xeno_loc = get_turf(owner) var/mob/living/carbon/alien/larva/new_xeno = new(xeno_loc) - new_xeno.key = ghost.key + ghost.transfer_ckey(new_xeno, FALSE) SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention new_xeno.canmove = 0 //so we don't move during the bursting animation new_xeno.notransform = 1 diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 5ff78ec1c9..812733ebe2 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -912,7 +912,7 @@ if(mind) mind.transfer_to(new_mob) else - new_mob.key = key + transfer_ckey(new_mob) for(var/para in hasparasites()) var/mob/living/simple_animal/hostile/guardian/G = para diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 7fd861bfeb..0f8687397d 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -192,7 +192,7 @@ /mob/proc/makePAI(delold) var/obj/item/paicard/card = new /obj/item/paicard(get_turf(src)) var/mob/living/silicon/pai/pai = new /mob/living/silicon/pai(card) - pai.key = key + transfer_ckey(pai) pai.name = name card.setPersonality(pai) if(delold) diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 49261d6e38..46d9525046 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -915,7 +915,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(mind && paicard.pai) mind.transfer_to(paicard.pai) else if(paicard.pai) - paicard.pai.key = key + transfer_ckey(paicard.pai) else ghostize(0) // The pAI card that just got ejected was dead. key = null diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index 6d3121474c..d7d4d1b9f2 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -160,7 +160,7 @@ if(mind) mind.transfer_to(R, 1) else - R.key = key + transfer_ckey(R) qdel(src) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm b/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm index a655bdf231..e54b21724d 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm @@ -61,5 +61,5 @@ var/obj/item/new_hat = new hat_type(D) D.equip_to_slot_or_del(new_hat, SLOT_HEAD) D.flags_1 |= (flags_1 & ADMIN_SPAWNED_1) - D.key = user.key + user.transfer_ckey(D, FALSE) qdel(src) diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index 5aec56b1e7..e3ef14c784 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -429,9 +429,9 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians to_chat(G, "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.") to_chat(src, "Your [G.real_name] has been successfully reset.") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(G)])") - G.ghostize(0) + G.ghostize(FALSE) G.setthemename(G.namedatum.theme) //give it a new color, to show it's a new person - G.key = C.key + C.transfer_ckey(G) G.reset = 1 switch(G.namedatum.theme) if("tech") diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index cfdf302d6b..f5b1706f87 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -90,7 +90,7 @@ if(key) to_chat(user, "Someone else already took this spider.") return 1 - key = user.key + user.transfer_ckey(src, FALSE) return 1 //nursemaids - these create webs and eggs diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index b529d826c9..b43bf2bb51 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -585,7 +585,7 @@ Difficulty: Very Hard var/be_helper = alert("Become a Lightgeist? (Warning, You can no longer be cloned!)",,"Yes","No") if(be_helper == "Yes" && !QDELETED(src) && isobserver(user)) var/mob/living/simple_animal/hostile/lightgeist/W = new /mob/living/simple_animal/hostile/lightgeist(get_turf(loc)) - W.key = user.key + user.transfer_ckey(W, FALSE) /obj/machinery/anomalous_crystal/helpers/Topic(href, href_list) @@ -649,7 +649,7 @@ Difficulty: Very Hard L.heal_overall_damage(heal_power, heal_power) new /obj/effect/temp_visual/heal(get_turf(target), "#80F5FF") -/mob/living/simple_animal/hostile/lightgeist/ghostize() +/mob/living/simple_animal/hostile/lightgeist/ghostize(can_reenter_corpse = TRUE, send_the_signal = TRUE) . = ..() if(.) death() diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 86f63a729d..5e2798cc8e 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -916,7 +916,7 @@ if(mind) mind.transfer_to(G) else - G.key = key + transfer_ckey(G) ..(gibbed) /mob/living/simple_animal/parrot/Poly/proc/Read_Memory() diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm index a3e2f48b75..bf80ab9ff4 100644 --- a/code/modules/mob/living/simple_animal/slime/powers.dm +++ b/code/modules/mob/living/simple_animal/slime/powers.dm @@ -198,7 +198,7 @@ if(src.mind) src.mind.transfer_to(new_slime) else - new_slime.key = src.key + transfer_ckey(new_slime) qdel(src) else to_chat(src, "I am not ready to reproduce yet...") diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index bd3c813b12..5a7c6e21ab 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -444,7 +444,13 @@ // M.Login() //wat return - +/mob/proc/transfer_ckey(mob/new_mob, send_signal = TRUE) + if(!ckey) + return FALSE + if(send_signal) + SEND_SIGNAL(src, COMSIG_MOB_KEY_CHANGE, new_mob, src) + new_mob.ckey = ckey + return TRUE /mob/verb/cancel_camera() set name = "Cancel Camera View" diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 776bd04935..5519c9be95 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -429,8 +429,8 @@ It's fairly easy to fix if dealing with single letters but not so much with comp var/mob/dead/observer/C = pick(candidates) to_chat(M, "Your mob has been taken over by a ghost!") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(M)])") - M.ghostize(0) - M.key = C.key + M.ghostize(FALSE, TRUE) + C.transfer_ckey(M, FALSE) return TRUE else to_chat(M, "There were no ghosts willing to take control.") diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm index 673548ff48..a11e7a228e 100644 --- a/code/modules/mob/mob_transformation_simple.dm +++ b/code/modules/mob/mob_transformation_simple.dm @@ -53,7 +53,7 @@ if(mind && isliving(M)) mind.transfer_to(M, 1) // second argument to force key move to new mob else - M.key = key + transfer_ckey(M) if(delete_old_mob) QDEL_IN(src, 1) diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 6bbe512776..6394b45aa7 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -382,7 +382,7 @@ mind.active = FALSE mind.transfer_to(R) else if(transfer_after) - R.key = key + transfer_ckey(R) R.apply_pref_name("cyborg") @@ -401,7 +401,7 @@ qdel(src) //human -> alien -/mob/living/carbon/human/proc/Alienize() +/mob/living/carbon/human/proc/Alienize(mind_transfer = TRUE) if (notransform) return for(var/obj/item/W in src) @@ -425,13 +425,16 @@ new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc) new_xeno.a_intent = INTENT_HARM - new_xeno.key = key + if(mind && mind_transfer) + mind.transfer_to(new_xeno) + else + transfer_ckey(new_xeno) to_chat(new_xeno, "You are now an alien.") . = new_xeno qdel(src) -/mob/living/carbon/human/proc/slimeize(reproduce as num) +/mob/living/carbon/human/proc/slimeize(reproduce, mind_transfer = TRUE) if (notransform) return for(var/obj/item/W in src) @@ -457,20 +460,26 @@ else new_slime = new /mob/living/simple_animal/slime(loc) new_slime.a_intent = INTENT_HARM - new_slime.key = key + if(mind && mind_transfer) + mind.transfer_to(new_slime) + else + transfer_ckey(new_slime) to_chat(new_slime, "You are now a slime. Skreee!") . = new_slime qdel(src) -/mob/proc/become_overmind(starting_points = 60) +/mob/proc/become_overmind(starting_points = 60, mind_transfer = FALSE) var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points) - B.key = key + if(mind && mind_transfer) + mind.transfer_to(B) + else + transfer_ckey(B) . = B qdel(src) -/mob/living/carbon/human/proc/corgize() +/mob/living/carbon/human/proc/corgize(mind_transfer = TRUE) if (notransform) return for(var/obj/item/W in src) @@ -485,13 +494,16 @@ var/mob/living/simple_animal/pet/dog/corgi/new_corgi = new /mob/living/simple_animal/pet/dog/corgi (loc) new_corgi.a_intent = INTENT_HARM - new_corgi.key = key + if(mind && mind_transfer) + mind.transfer_to(new_corgi) + else + transfer_ckey(new_corgi) to_chat(new_corgi, "You are now a Corgi. Yap Yap!") . = new_corgi qdel(src) -/mob/living/carbon/proc/gorillize() +/mob/living/carbon/proc/gorillize(mind_transfer = TRUE) if(notransform) return @@ -509,22 +521,22 @@ invisibility = INVISIBILITY_MAXIMUM var/mob/living/simple_animal/hostile/gorilla/new_gorilla = new (get_turf(src)) new_gorilla.a_intent = INTENT_HARM - if(mind) + if(mind && mind_transfer) mind.transfer_to(new_gorilla) else - new_gorilla.key = key + transfer_ckey(new_gorilla) to_chat(new_gorilla, "You are now a gorilla. Ooga ooga!") . = new_gorilla qdel(src) -/mob/living/carbon/human/Animalize() +/mob/living/carbon/human/Animalize(mind_transfer = TRUE) var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes - - if(!safe_animal(mobpath)) - to_chat(usr, "Sorry but this mob type is currently unavailable.") + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes + if(!mobpath) return + if(mind) + mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE if(notransform) return @@ -532,8 +544,8 @@ dropItemToGround(W) regenerate_icons() - notransform = 1 - canmove = 0 + notransform = TRUE + canmove = FALSE icon = null invisibility = INVISIBILITY_MAXIMUM @@ -541,8 +553,10 @@ qdel(t) var/mob/new_mob = new mobpath(src.loc) - - new_mob.key = key + if(mind && mind_transfer) + mind.transfer_to(new_mob) + else + transfer_ckey(new_mob) new_mob.a_intent = INTENT_HARM @@ -550,59 +564,23 @@ . = new_mob qdel(src) -/mob/proc/Animalize() +/mob/proc/Animalize(mind_transfer = TRUE) var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes - - if(!safe_animal(mobpath)) - to_chat(usr, "Sorry but this mob type is currently unavailable.") + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes + if(!mobpath) return + if(mind) + mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE var/mob/new_mob = new mobpath(src.loc) - new_mob.key = key + if(mind && mind_transfer) + mind.transfer_to(new_mob) + else + transfer_ckey(new_mob) new_mob.a_intent = INTENT_HARM to_chat(new_mob, "You feel more... animalistic") . = new_mob qdel(src) - -/* Certain mob types have problems and should not be allowed to be controlled by players. - * - * This proc is here to force coders to manually place their mob in this list, hopefully tested. - * This also gives a place to explain -why- players shouldnt be turn into certain mobs and hopefully someone can fix them. - */ -/mob/proc/safe_animal(MP) - -//Bad mobs! - Remember to add a comment explaining what's wrong with the mob - if(!MP) - return 0 //Sanity, this should never happen. - - if(ispath(MP, /mob/living/simple_animal/hostile/construct)) - return 0 //Verbs do not appear for players. - -//Good mobs! - if(ispath(MP, /mob/living/simple_animal/pet/cat)) - return 1 - if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi)) - return 1 - if(ispath(MP, /mob/living/simple_animal/crab)) - return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/carp)) - return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/mushroom)) - return 1 - if(ispath(MP, /mob/living/simple_animal/shade)) - return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/killertomato)) - return 1 - if(ispath(MP, /mob/living/simple_animal/mouse)) - return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak) - if(ispath(MP, /mob/living/simple_animal/hostile/bear)) - return 1 //Bears will auto-attack mobs, even if they're player controlled (Fixed! - Nodrak) - if(ispath(MP, /mob/living/simple_animal/parrot)) - return 1 //Parrots are no longer unfinished! -Nodrak - - //Not in here? Must be untested! - return 0 diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index b062893954..c31fc58817 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -681,7 +681,7 @@ var/list/candidates = pollCandidatesForMob("Do you want to play as [SM.name]?", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, SM, POLL_IGNORE_SENTIENCE_POTION) // see poll_ignore.dm if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) - SM.key = C.key + C.transfer_ckey(SM, FALSE) SM.mind.enslave_mind_to_creator(user) SM.sentience_act() to_chat(SM, "All at once it makes sense: you know what you are and who you are! Self awareness is yours!") diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm index ffdd270994..107f303049 100644 --- a/code/modules/spells/spell_types/mind_transfer.dm +++ b/code/modules/spells/spell_types/mind_transfer.dm @@ -70,12 +70,12 @@ Also, you never added distance checking after target is selected. I've went ahea var/mob/living/caster = user//The wizard/whomever doing the body transferring. //MIND TRANSFER BEGIN - var/mob/dead/observer/ghost = victim.ghostize(0) + var/mob/dead/observer/ghost = victim.ghostize(FALSE, TRUE) caster.mind.transfer_to(victim) ghost.mind.transfer_to(caster) if(ghost.key) - caster.key = ghost.key //have to transfer the key since the mind was not active + ghost.transfer_ckey(caster) //have to transfer the key since the mind was not active qdel(ghost) //MIND TRANSFER END diff --git a/modular_citadel/code/modules/mob/living/simple_animal/banana_spider.dm b/modular_citadel/code/modules/mob/living/simple_animal/banana_spider.dm index d2006cd12c..fdc271a158 100644 --- a/modular_citadel/code/modules/mob/living/simple_animal/banana_spider.dm +++ b/modular_citadel/code/modules/mob/living/simple_animal/banana_spider.dm @@ -38,7 +38,7 @@ return to_chat(user, "You decide to wake up the banana spider...") awakening = 1 - + spawn(30) if(!QDELETED(src)) var/mob/living/simple_animal/banana_spider/S = new /mob/living/simple_animal/banana_spider(get_turf(src.loc)) @@ -98,7 +98,7 @@ if(be_spider == "No" || QDELETED(src) || !isobserver(user)) return sentience_act() - key = user.key + user.transfer_ckey(src, FALSE) density = TRUE /mob/living/simple_animal/banana_spider/ComponentInitialize() diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/SDGF.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/SDGF.dm index c405bb1a88..a7e1ac8e92 100644 --- a/modular_citadel/code/modules/reagents/chemistry/reagents/SDGF.dm +++ b/modular_citadel/code/modules/reagents/chemistry/reagents/SDGF.dm @@ -86,7 +86,7 @@ IMPORTANT FACTORS TO CONSIDER WHILE BALANCING candies = shuffle(candies)//Shake those ghosts up! for(var/mob/dead/observer/C2 in candies) if(C2.key && C2) - SM.key = C2.key + C2.transfer_ckey(SM, FALSE) message_admins("Ghost candidate found! [C2] key [C2.key] is becoming a clone of [M] key: [M.key] (They agreed to respect the character they're becoming, and agreed to not ERP without express permission from the original.)") log_game("FERMICHEM: [M] ckey: [M.key] is creating a clone, controlled by [C2]") break diff --git a/tgstation.dme b/tgstation.dme index 1e632e4fa6..dc556999d2 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -382,6 +382,7 @@ #include "code\datums\components\swarming.dm" #include "code\datums\components\thermite.dm" #include "code\datums\components\uplink.dm" +#include "code\datums\components\virtual_reality.dm" #include "code\datums\components\wearertargeting.dm" #include "code\datums\components\wet_floor.dm" #include "code\datums\components\decals\blood.dm" @@ -2884,7 +2885,7 @@ #include "code\modules\vending\toys.dm" #include "code\modules\vending\wardrobes.dm" #include "code\modules\vending\youtool.dm" -#include "code\modules\VR\vr_human.dm" +#include "code\modules\VR\vr_mob.dm" #include "code\modules\VR\vr_sleeper.dm" #include "code\modules\zombie\items.dm" #include "code\modules\zombie\organs.dm" diff --git a/tgui/src/interfaces/vr_sleeper.ract b/tgui/src/interfaces/vr_sleeper.ract index 32f3ab33b3..91074a9425 100644 --- a/tgui/src/interfaces/vr_sleeper.ract +++ b/tgui/src/interfaces/vr_sleeper.ract @@ -22,12 +22,14 @@ {{data.vr_avatar.name}} - - {{data.vr_avatar.status}} - - - {{Math.round(adata.vr_avatar.health)}}/{{adata.vr_avatar.maxhealth}} - + {{#if data.isliving}} + + {{data.vr_avatar.status}} + + + {{Math.round(adata.vr_avatar.health)}}/{{adata.vr_avatar.maxhealth}} + + {{/if}} {{else}}