Base implementation of /datum/persistent_client (#89449)

## About The Pull Request
Converts `/datum/player_details` into `/datum/persistent_client`.
Persistent Clients persist across connections. The only time a mob's
persistent client will change is if the ckey it's bound to logs into a
different mob, or the mob is deleted (duh).

Also adds PossessByPlayer() so that transfering mob control is cleaner
and makes more immediate sense if you don't know byond-fu.

## Why It's Good For The Game
Clients are an abstract representation of a connection that can be
dropped at almost any moment so putting things that should be stable to
access at any time onto an undying object is ideal. This allows for
future expansions like abstracting away client.screen and managing
everything cleanly.
This commit is contained in:
Kapu1178
2025-02-25 13:52:24 -06:00
committed by GitHub
parent 575972e3f8
commit a0e862d575
87 changed files with 226 additions and 173 deletions
+3
View File
@@ -49,3 +49,6 @@
/// A null statement to guard against EmptyBlock lint without necessitating the use of pass()
/// Used to avoid proc-call overhead. But use sparingly. Probably pointless in most places.
#define EMPTY_BLOCK_GUARD ;
/// Abstraction over using mob.client to just check if there's a connected player.
#define HAS_CONNECTED_PLAYER(mob) (mob.client)
+1 -1
View File
@@ -184,7 +184,7 @@
ghost_player.client.prefs.safe_transfer_prefs_to(new_character)
new_character.dna.update_dna_identity()
new_character.key = ghost_player.key
new_character.PossessByPlayer(ghost_player.ckey)
return new_character
+5 -6
View File
@@ -18,11 +18,10 @@
// Cannot use the list as a map if the key is a number, so we stringify it (thank you BYOND)
var/smessage_type = num2text(message_type, MAX_BITFLAG_DIGITS)
var/datum/player_details/client_details = client?.player_details
if(!isnull(client_details))
if(!islist(client_details.logging[smessage_type]))
client_details.logging[smessage_type] = list()
if(HAS_CONNECTED_PLAYER(src))
if(!islist(persistent_client.logging[smessage_type]))
persistent_client.logging[smessage_type] = list()
if(!islist(logging[smessage_type]))
logging[smessage_type] = list()
@@ -51,7 +50,7 @@
logging[smessage_type] += timestamped_message
if(client)
client.player_details.logging[smessage_type] += timestamped_message
if(HAS_CONNECTED_PLAYER(src))
persistent_client.logging[smessage_type] += timestamped_message
..()
+1 -1
View File
@@ -661,7 +661,7 @@ GLOBAL_LIST_INIT(achievements_unlocked, list())
/datum/controller/subsystem/ticker/proc/give_show_report_button(client/C)
var/datum/action/report/R = new
C.player_details.player_actions += R
C.persistent_client.player_actions += R
R.Grant(C.mob)
to_chat(C,span_infoplain("<a href='byond://?src=[REF(R)];report=1'>Show roundend report again</a>"))
+6 -6
View File
@@ -59,10 +59,9 @@ SUBSYSTEM_DEF(achievements)
most_unlocked_achievement = instance
qdel(query)
for(var/i in GLOB.clients)
var/client/C = i
if(!C.player_details.achievements.initialized)
C.player_details.achievements.InitializeData()
for(var/client/C in GLOB.clients)
if(!C.persistent_client.achievements.initialized)
C.persistent_client.achievements.InitializeData()
return SS_INIT_SUCCESS
@@ -71,11 +70,12 @@ SUBSYSTEM_DEF(achievements)
/datum/controller/subsystem/achievements/proc/save_achievements_to_db()
var/list/cheevos_to_save = list()
for(var/ckey in GLOB.player_details)
var/datum/player_details/PD = GLOB.player_details[ckey]
for(var/ckey in GLOB.persistent_clients_by_ckey)
var/datum/persistent_client/PD = GLOB.persistent_clients_by_ckey[ckey]
if(!PD || !PD.achievements)
continue
cheevos_to_save += PD.achievements.get_changed_data()
if(!length(cheevos_to_save))
return
SSdbcore.MassInsert(format_table_name("achievements"), cheevos_to_save, duplicate_key = TRUE)
+2 -3
View File
@@ -86,9 +86,8 @@ SUBSYSTEM_DEF(blackbox)
if (MS.rc_msgs.len)
record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console")
for(var/player_key in GLOB.player_details)
var/datum/player_details/PD = GLOB.player_details[player_key]
record_feedback("tally", "client_byond_version", 1, PD.full_byond_version())
for(var/datum/persistent_client/PC as anything in GLOB.persistent_clients)
record_feedback("tally", "client_byond_version", 1, PC.full_byond_version())
/datum/controller/subsystem/blackbox/Shutdown()
sealed = FALSE
@@ -538,7 +538,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
new_xeno.PossessByPlayer(applicant.ckey)
new_xeno.move_into_vent(vent)
message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.")
log_dynamic("[key_name(new_xeno)] was spawned as an alien by the midround ruleset.")
@@ -696,7 +696,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/generate_ruleset_body(mob/applicant)
var/mob/living/carbon/human/ninja = create_space_ninja(pick(spawn_locs))
ninja.key = applicant.key
ninja.PossessByPlayer(applicant.ckey)
ninja.mind.add_antag_datum(/datum/antagonist/ninja)
message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a Space Ninja by the midround ruleset.")
@@ -769,7 +769,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant)
var/mob/living/basic/revenant/revenant = new(pick(spawn_locs))
revenant.key = applicant.key
revenant.PossessByPlayer(applicant.ckey)
message_admins("[ADMIN_LOOKUPFLW(revenant)] has been made into a revenant by the midround ruleset.")
log_game("[key_name(revenant)] was spawned as a revenant by the midround ruleset.")
return revenant
+5 -5
View File
@@ -240,7 +240,7 @@ SUBSYSTEM_DEF(vote)
voting_action.name = "Vote: [current_vote.override_question || current_vote.name]"
voting_action.Grant(new_voter.mob)
new_voter.player_details.player_actions += voting_action
new_voter.persistent_client.player_actions += voting_action
generated_actions += voting_action
if(current_vote.vote_sound && new_voter.prefs.read_preference(/datum/preference/toggle/sound_announcements))
SEND_SOUND(new_voter, sound(current_vote.vote_sound))
@@ -472,12 +472,12 @@ SUBSYSTEM_DEF(vote)
// We also need to remove our action from the player actions when we're cleaning up.
/datum/action/vote/Remove(mob/removed_from)
if(removed_from.client)
removed_from.client?.player_details.player_actions -= src
if(removed_from.persistent_client)
removed_from.persistent_client.player_actions -= src
else if(removed_from.ckey)
var/datum/player_details/associated_details = GLOB.player_details[removed_from.ckey]
associated_details?.player_actions -= src
var/datum/persistent_client/persistent_client = GLOB.persistent_clients_by_ckey[removed_from.ckey]
persistent_client?.player_actions -= src
return ..()
@@ -131,4 +131,4 @@
set name = "Check achievements"
set desc = "See all of your achievements!"
player_details.achievements.ui_interact(usr)
persistent_client.achievements.ui_interact(usr)
+3 -1
View File
@@ -37,14 +37,16 @@
/datum/award/score/progress/fish/proc/validate_early_joiners(datum/source)
for(var/client/client as anything in GLOB.clients)
var/datum/achievement_data/holder = client.player_details.achievements
var/datum/achievement_data/holder = client.persistent_client.achievements
if(!holder?.initialized)
continue
var/list/entries = holder.data[/datum/award/score/progress/fish]
var/list_copied = FALSE
for(var/fish_id in entries)
if(SSfishing.catchable_fish[fish_id])
continue
//make a new list, unbound from the cached awards data, so that the score can be updated at the end of the round.
if(!list_copied)
entries = entries.Copy()
+1 -1
View File
@@ -69,7 +69,7 @@
qdel(src)
return
friend.key = ghost.key
friend.PossessByPlayer(ghost.ckey)
friend.attach_to_owner(owner)
friend.setup_appearance()
friend_initialized = TRUE
@@ -56,7 +56,7 @@
qdel(src)
return
stranger_backseat.key = ghost.key
stranger_backseat.PossessByPlayer(ghost.ckey)
stranger_backseat.log_message("became [key_name(owner)]'s split personality.", LOG_GAME)
message_admins("[ADMIN_LOOKUPFLW(stranger_backseat)] became [ADMIN_LOOKUPFLW(owner)]'s split personality.")
@@ -222,7 +222,7 @@
set waitfor = FALSE
var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger("[owner.real_name]'s")] brainwashed mind?", poll_time = 7.5 SECONDS, checked_target = stranger_backseat, alert_pic = owner, role_name_text = "brainwashed mind")
if(chosen_one)
stranger_backseat.key = chosen_one.key
stranger_backseat.PossessByPlayer(chosen_one.ckey)
else
qdel(src)
@@ -145,7 +145,7 @@
harbinger.log_message("took control of [new_body].", LOG_GAME)
// doesn't transfer mind because that transfers antag datum as well
new_body.key = harbinger.key
new_body.PossessByPlayer(harbinger.ckey)
// Already qdels due to below proc but just in case
qdel(src)
@@ -101,7 +101,7 @@
aliver.death()
aliver.visible_message(span_deadsay("[aliver.name]'s soul is struggling to return!"))
else
aliver.key = chosen_one.key
aliver.PossessByPlayer(chosen_one.ckey)
on_successful_revive?.Invoke(aliver)
qdel(src)
+2 -3
View File
@@ -74,7 +74,7 @@
if(affected_mob.mind)
affected_mob.mind.transfer_to(new_mob)
else
new_mob.key = affected_mob.key
new_mob.PossessByPlayer(affected_mob.ckey)
if(transformed_antag_datum)
new_mob.mind.add_antag_datum(transformed_antag_datum)
new_mob.name = affected_mob.real_name
@@ -89,14 +89,13 @@
to_chat(affected_mob, span_userdanger("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(chosen_one)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.")
affected_mob.ghostize(FALSE)
affected_mob.key = chosen_one.key
affected_mob.PossessByPlayer(chosen_one.ckey)
else
to_chat(new_mob, span_userdanger("Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!"))
new_mob.investigate_log("has been killed because there was no one to replace them as a job-banned player.", INVESTIGATE_DEATHS)
new_mob.death()
if (!QDELETED(new_mob))
new_mob.ghostize(can_reenter_corpse = FALSE)
new_mob.key = null
/datum/disease/transformation/jungle_flu
name = "Jungle Flu"
+1 -1
View File
@@ -205,7 +205,7 @@
RegisterSignal(new_character, COMSIG_LIVING_DEATH, PROC_REF(set_death_time))
if(active || force_key_move)
new_character.key = key //now transfer the key to link the client to our new body
new_character.PossessByPlayer(key) //now transfer the key to link the client to our new body
if(new_character.client)
LAZYCLEARLIST(new_character.client.recent_examines)
new_character.client.init_verbs() // re-initialize character specific verbs
@@ -183,7 +183,7 @@
var/mob/living/basic/ghost/swarm/new_ghost = new(get_turf(src))
ghosts_spawned += new_ghost
new_ghost.ghostize(FALSE)
new_ghost.key = candidate_ghost.key
new_ghost.PossessByPlayer(candidate_ghost.key)
var/policy = get_policy(ROLE_ANOMALY_GHOST)
if(policy)
to_chat(new_ghost, policy)
@@ -53,7 +53,7 @@
// Failed to find a player, or somehow detonated prematurely
if(isnull(chosen_one))
return
pyro.key = chosen_one.key
pyro.PossessByPlayer(chosen_one.key)
pyro.mind.special_role = ROLE_PYROCLASTIC_SLIME
pyro.mind.add_antag_datum(/datum/antagonist/pyro_slime)
pyro.log_message("was made into a slime by pyroclastic anomaly", LOG_GAME)
+1 -1
View File
@@ -432,7 +432,7 @@
var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger("[user.real_name]'s")] [span_notice("Servant")]?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 5 SECONDS, checked_target = human_servant, alert_pic = user, role_name_text = "dice servant")
if(chosen_one)
message_admins("[ADMIN_LOOKUPFLW(chosen_one)] was spawned as Dice Servant")
human_servant.key = chosen_one.key
human_servant.PossessByPlayer(chosen_one.key)
human_servant.equipOutfit(/datum/outfit/butler)
var/datum/mind/servant_mind = new /datum/mind()
+1 -1
View File
@@ -286,7 +286,7 @@ ADMIN_VERB(create_or_modify_area, R_DEBUG, "Create Or Modify Area", "Create of m
log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].")
BLACKBOX_LOG_ADMIN_VERB("Ghost Drag Control")
tomob.key = frommob.key
tomob.PossessByPlayer(frommob.key)
tomob.client?.init_verbs()
qdel(frommob)
+1 -1
View File
@@ -112,7 +112,7 @@
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(body)])")
body.ghostize(FALSE)
body.key = C.key
body.PossessByPlayer(C.key)
if (make_antag)
body.mind.add_antag_datum(antag_type)
continue
+2 -3
View File
@@ -283,9 +283,8 @@
M_rname_as_key = null
var/previous_names_string = ""
var/datum/player_details/readable = GLOB.player_details[M.ckey]
if(readable)
previous_names_string = readable.get_played_names()
if(M.persistent_client)
previous_names_string = M.persistent_client.get_played_names()
//output for each mob
dat += {"
+5 -6
View File
@@ -62,11 +62,10 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mo
body += "<b>Mob type</b> = [player.type]<br><br>"
if(player.client)
if(HAS_CONNECTED_PLAYER(player))
body += "<b>Old names:</b> "
var/datum/player_details/deets = GLOB.player_details[player.ckey]
if(deets)
body += deets.get_played_names()
if(player.persistent_client)
body += player.persistent_client.get_played_names()
else
body += "<i>None?!</i>"
body += "<br><br>"
@@ -196,7 +195,7 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th
var/mob/living/carbon/human/species/monkey/new_monkey = new
SSjob.send_to_late_join(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
new_monkey.PossessByPlayer(G_found.key)
to_chat(new_monkey, "You have been fully respawned. Enjoy the game.", confidential = TRUE)
var/msg = span_adminnotice("[key_name_admin(user)] has respawned [new_monkey.key] as a filthy monkey.")
message_admins(msg)
@@ -231,7 +230,7 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th
if(is_unassigned_job(new_character.mind.assigned_role))
new_character.mind.set_assigned_role(SSjob.get_job_type(SSjob.overflow_role))
new_character.key = G_found.key
new_character.PossessByPlayer(G_found.key)
/*
The code below functions with the assumption that the mob is already a traitor if they have a special role.
+3 -1
View File
@@ -167,7 +167,9 @@ ADMIN_VERB(cmd_assume_direct_control, R_ADMIN, "Assume Direct Control", "Assume
var/mob/adminmob = user.mob
if(M.ckey)
M.ghostize(FALSE)
M.key = user.key
M.PossessByPlayer(user.key)
user.init_verbs()
if(isobserver(adminmob))
qdel(adminmob)
+2 -2
View File
@@ -174,7 +174,7 @@
var/chosen_outfit = usr.client?.prefs?.read_preference(/datum/preference/choiced/brief_outfit)
usr.client.prefs.safe_transfer_prefs_to(admin_officer, is_antag = TRUE)
admin_officer.equipOutfit(chosen_outfit)
admin_officer.key = usr.key
admin_officer.PossessByPlayer(usr.key)
else
to_chat(usr, span_warning("Could not spawn you in as briefing officer as you are not a ghost!"))
@@ -232,7 +232,7 @@
else
ert_operative = new /mob/living/carbon/human(spawnloc)
chosen_candidate.client.prefs.safe_transfer_prefs_to(ert_operative, is_antag = TRUE)
ert_operative.key = chosen_candidate.key
ert_operative.PossessByPlayer(chosen_candidate.key)
if(ertemplate.enforce_human || !(ert_operative.dna.species.changesource_flags & ERT_SPAWN))
ert_operative.set_species(/datum/species/human)
@@ -48,10 +48,9 @@
dat += "<hr style='background:#000000; border:0; height:1px'>"
var/log_source = M.logging
if(source == LOGSRC_CKEY && M.ckey)
var/datum/player_details/details = GLOB.player_details[M.ckey]
if(details) //we dont want to runtime if an admin aghosted
log_source = details.logging
if(source == LOGSRC_CKEY && M.persistent_client)
log_source = M.persistent_client.logging
var/list/concatenated_logs = list()
for(var/log_type in log_source)
var/nlog_type = text2num(log_type)
@@ -59,6 +58,7 @@
var/list/all_the_entrys = log_source[log_type]
for(var/entry in all_the_entrys)
concatenated_logs += "<b>[entry]</b><br>[all_the_entrys[entry]]"
if(length(concatenated_logs))
sortTim(concatenated_logs, cmp = GLOBAL_PROC_REF(cmp_text_dsc)) //Sort by timestamp.
dat += "<font size=2px>"
+2 -2
View File
@@ -639,7 +639,7 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w
chosen_candidate = pick(candidates)
candidates -= chosen_candidate
nerd = new /mob/living/basic/drone/classic(spawnpoint)
nerd.key = chosen_candidate.key
nerd.PossessByPlayer(chosen_candidate.key)
nerd.log_message("has been selected as a Nanotrasen emergency response drone.", LOG_GAME)
teamsize--
@@ -716,7 +716,7 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w
var/mob/chosen = players[1]
if (chosen.client)
chosen.client.prefs.safe_transfer_prefs_to(spawnedMob, is_antag = TRUE)
spawnedMob.key = chosen.key
spawnedMob.PossessByPlayer(chosen.key)
players -= chosen
if (ishuman(spawnedMob) && ispath(humanoutfit, /datum/outfit))
var/mob/living/carbon/human/H = spawnedMob
@@ -306,7 +306,7 @@ GLOBAL_LIST_EMPTY(antagonists)
message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(owner)]) to replace antagonist banned player.")
log_game("[key_name(chosen_one)] has taken control of ([key_name(owner)]) to replace antagonist banned player.")
owner.current.ghostize(FALSE)
owner.current.key = chosen_one.key
owner.current.PossessByPlayer(chosen_one.key)
else
log_game("Couldn't find antagonist ban replacement for ([key_name(owner)]).")
@@ -76,7 +76,7 @@
new /obj/effect/particle_effect/fluid/smoke(T)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.safe_transfer_prefs_to(M, is_antag = TRUE)
M.key = C.key
M.PossessByPlayer(C.key)
var/datum/mind/app_mind = M.mind
var/datum/antagonist/wizard/apprentice/app = new()
@@ -247,7 +247,7 @@
borg.mmi.brainmob.name = brainopsname
borg.real_name = borg.name
borg.key = C.key
borg.PossessByPlayer(C.key)
borg.mind.add_antag_datum(antag_datum, creator_op ? creator_op.get_team() : null)
borg.mind.special_role = special_role_name
@@ -290,7 +290,7 @@
var/mob/living/basic/demon/spawned = new demon_type(T)
new /obj/effect/dummy/phased_mob(T, spawned)
spawned.key = C.key
spawned.PossessByPlayer(C.key)
/obj/item/antag_spawner/slaughter_demon/laughter
name = "vial of tickles"
@@ -33,7 +33,7 @@
if(origin && (origin.current ? (origin.current.stat == DEAD) : origin.get_ghost()))
origin.transfer_to(spawned_monkey)
spawned_monkey.key = origin.key
spawned_monkey.PossessByPlayer(origin.key)
var/datum/antagonist/changeling/changeling_datum = origin.has_antag_datum(/datum/antagonist/changeling)
if(!changeling_datum)
changeling_datum = origin.add_antag_datum(/datum/antagonist/changeling/headslug)
+1 -1
View File
@@ -371,7 +371,7 @@ Striking a noncultist, however, will tear their flesh."}
// Get the heretic's new body and antag datum.
trapped_entity = trapped_mind?.current
trapped_entity.key = trapped_mind?.key
trapped_entity.PossessByPlayer(trapped_mind?.key)
var/datum/antagonist/heretic/heretic_holder = GET_HERETIC(trapped_entity)
if(!heretic_holder)
stack_trace("[soul_to_bind] in but not a heretic on the heretic soul blade.")
+2 -2
View File
@@ -771,7 +771,7 @@ GLOBAL_VAR_INIT(narsie_summon_count, 0)
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(chosen_one)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.")
mob_to_revive.ghostize(FALSE)
mob_to_revive.key = chosen_one.key
mob_to_revive.PossessByPlayer(chosen_one.key)
else
fail_invoke()
return
@@ -1035,7 +1035,7 @@ GLOBAL_VAR_INIT(narsie_summon_count, 0)
old_mind = ghost_to_spawn.mind, \
old_body = ghost_to_spawn.mind.current, \
)
new_human.key = ghost_to_spawn.key
new_human.PossessByPlayer(ghost_to_spawn.key)
var/datum/antagonist/cult/created_cultist = new_human.mind?.add_antag_datum(/datum/antagonist/cult)
created_cultist?.silent = TRUE
to_chat(new_human, span_cult_italic("<b>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.</b>"))
@@ -424,7 +424,7 @@
summoned.move_resist = initial(summoned.move_resist)
summoned.ghostize(FALSE)
summoned.key = chosen_one.key
summoned.PossessByPlayer(chosen_one.key)
user.log_message("created a [summoned.name], controlled by [key_name(chosen_one)].", LOG_GAME)
message_admins("[ADMIN_LOOKUPFLW(user)] created a [summoned.name], [ADMIN_LOOKUPFLW(summoned)].")
@@ -167,7 +167,7 @@
return FALSE
message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(soon_to_be_ghoul)]) to replace an AFK player.")
soon_to_be_ghoul.ghostize(FALSE)
soon_to_be_ghoul.key = chosen_one.key
soon_to_be_ghoul.PossessByPlayer(chosen_one.key)
selected_atoms -= soon_to_be_ghoul
make_ghoul(user, soon_to_be_ghoul)
@@ -73,7 +73,7 @@
return FALSE
var/monster_type = pick(monster_types)
var/mob/living/monster = new monster_type(loc)
monster.key = user.key
monster.PossessByPlayer(user.key)
monster.set_name()
var/datum/antagonist/heretic_monster/woohoo_free_antag = new(src)
monster.mind.add_antag_datum(woohoo_free_antag)
@@ -194,7 +194,7 @@
var/mob/living/carbon/human/nukie = new(spawn_loc)
chosen_one.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE)
nukie.key = chosen_one.key
nukie.PossessByPlayer(chosen_one.key)
var/datum/antagonist/nukeop/antag_datum = new()
antag_datum.send_to_spawnpoint = FALSE
@@ -265,7 +265,7 @@
if(!is_listed)
ckey_list += user.ckey
newcarp.key = user.key
newcarp.PossessByPlayer(user.key)
newcarp.set_name()
var/datum/antagonist/space_carp/carp_antag = new(src)
newcarp.mind.add_antag_datum(carp_antag)
@@ -392,7 +392,7 @@
soulstone_spirit.AddComponent(/datum/component/soulstoned, src)
soulstone_spirit.name = "Shade of [victim.real_name]"
soulstone_spirit.real_name = "Shade of [victim.real_name]"
soulstone_spirit.key = shade_controller.key
soulstone_spirit.PossessByPlayer(shade_controller.key)
soulstone_spirit.copy_languages(victim, LANGUAGE_MIND)//Copies the old mobs languages into the new mob holder.
if(user)
soulstone_spirit.copy_languages(user, LANGUAGE_MASTER)
@@ -516,7 +516,7 @@
seek_master.Grant(newstruct)
if (isnull(target.mind))
newstruct.key = target.key
newstruct.PossessByPlayer(target.key)
else
target.mind.transfer_to(newstruct, force_key_move = TRUE)
var/atom/movable/screen/alert/bloodsense/sense_alert
+1 -1
View File
@@ -198,7 +198,7 @@ GLOBAL_VAR(basketball_game)
old_mind = player_client.mob.mind, \
old_body = player_client.mob.mind.current, \
)
baller.key = player_key
baller.PossessByPlayer(player_key)
SEND_SOUND(baller, sound('sound/items/whistle/whistle.ogg', volume=30))
if(is_player_referee)
@@ -32,7 +32,7 @@
server_ref = WEAKREF(server)
server.avatar_connection_refs.Add(WEAKREF(src))
avatar.key = old_body.key
avatar.PossessByPlayer(old_body.key)
ADD_TRAIT(avatar, TRAIT_NO_MINDSWAP, REF(src)) // do not remove this one
ADD_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src))
+1 -1
View File
@@ -111,7 +111,7 @@
mutation_target.gib(DROP_ALL_REMAINS)
var/datum/mind/ghost_mind = ghost.mind
new_mob.key = ghost.key
new_mob.PossessByPlayer(ghost.key)
if(ghost_mind)
new_mob.AddComponent(/datum/component/temporary_body, ghost_mind, ghost_mind.current, TRUE)
+2 -2
View File
@@ -30,7 +30,7 @@
holder = c
buttons = list()
li_cb = CALLBACK(src, PROC_REF(post_login))
holder.player_details.post_login_callbacks += li_cb
holder.persistent_client.post_login_callbacks += li_cb
holder.show_popup_menus = FALSE
create_buttons()
holder.screen += buttons
@@ -47,7 +47,7 @@
/datum/buildmode/Destroy()
close_switchstates()
close_preview()
holder.player_details.post_login_callbacks -= li_cb
holder.persistent_client.post_login_callbacks -= li_cb
li_cb = null
holder = null
modebutton = null
+1 -1
View File
@@ -138,7 +138,7 @@
var/list/credits
///these persist between logins/logouts during the same round.
var/datum/player_details/player_details
var/datum/persistent_client/persistent_client
///Should only be a key-value list of north/south/east/west = atom/movable/screen.
var/list/char_render_holders
+15 -14
View File
@@ -249,6 +249,17 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
GLOB.clients += src
GLOB.directory[ckey] = src
var/reconnecting = FALSE
if(GLOB.persistent_clients_by_ckey[ckey])
reconnecting = TRUE
persistent_client = GLOB.persistent_clients_by_ckey[ckey]
persistent_client.byond_build = byond_build
persistent_client.byond_version = byond_version
else
persistent_client = new(ckey)
persistent_client.byond_build = byond_build
persistent_client.byond_version = byond_version
if(byond_version >= 516)
winset(src, null, list("browser-options" = "find,refresh,byondstorage"))
@@ -328,18 +339,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
else
message_admins(span_danger("<B>[message_type]: </B></span><span class='notice'>Connecting player [key_name_admin(src)] has the same [matches] as [joined_player_ckey](no longer logged in)<b>[in_round]</b>. "))
log_admin_private("[message_type]: Connecting player [key_name(src)] has the same [matches] as [joined_player_ckey](no longer logged in)[in_round].")
var/reconnecting = FALSE
if(GLOB.player_details[ckey])
reconnecting = TRUE
player_details = GLOB.player_details[ckey]
player_details.byond_version = byond_version
player_details.byond_build = byond_build
else
player_details = new(ckey)
player_details.byond_version = byond_version
player_details.byond_build = byond_build
GLOB.player_details[ckey] = player_details
. = ..() //calls mob.Login()
@@ -586,6 +585,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
GLOB.clients -= src
GLOB.directory -= ckey
persistent_client.client = null
log_access("Logout: [key_name(src)]")
GLOB.ahelp_tickets.ClientLogout(src)
GLOB.interviews.client_logout(src)
@@ -1040,11 +1041,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
///Redirect proc that makes it easier to call the unlock achievement proc. Achievement type is the typepath to the award, user is the mob getting the award, and value is an optional variable used for leaderboard value increments
/client/proc/give_award(achievement_type, mob/user, value = 1)
return player_details.achievements.unlock(achievement_type, user, value)
return persistent_client.achievements.unlock(achievement_type, user, value)
///Redirect proc that makes it easier to get the status of an achievement. Achievement type is the typepath to the award.
/client/proc/get_award_status(achievement_type, mob/user, value = 1)
return player_details.achievements.get_achievement_status(achievement_type)
return persistent_client.achievements.get_achievement_status(achievement_type)
///Gives someone hearted status for OOC, from behavior commendations
/client/proc/adjust_heart(duration = 24 HOURS)
@@ -1,55 +1,86 @@
///assoc list of ckey -> /datum/player_details
GLOBAL_LIST_EMPTY_TYPED(player_details, /datum/player_details)
///assoc list of ckey -> /datum/persistent_client
GLOBAL_LIST_EMPTY_TYPED(persistent_clients_by_ckey, /datum/persistent_client)
/// A flat list of all persistent clients, for her looping pleasure.
GLOBAL_LIST_EMPTY_TYPED(persistent_clients, /datum/persistent_client)
/// Tracks information about a client between log in and log outs
/datum/player_details
/datum/persistent_client
/// The true client
var/client/client
/// The mob this persistent client is currently bound to.
var/mob/mob
/// Major version of BYOND this client is using.
var/byond_version
/// Build number of BYOND this client is using.
var/byond_build
/// Action datums assigned to this player
var/list/datum/action/player_actions = list()
/// Tracks client action logging
var/list/logging = list()
/// Callbacks invoked when this client logs in again
var/list/post_login_callbacks = list()
/// Callbacks invoked when this client logs out
var/list/post_logout_callbacks = list()
/// List of names this key played under this round
/// assoc list of name -> mob tag
var/list/played_names = list()
/// Lazylist of preference slots this client has joined the round under
/// Numbers are stored as strings
var/list/joined_as_slots
/// Major version of BYOND this client is using.
var/byond_version
/// Build number of BYOND this client is using.
var/byond_build
/// Tracks achievements they have earned
var/datum/achievement_data/achievements
/// World.time this player last died
var/time_of_death = 0
/datum/player_details/New(key)
achievements = new(key)
/datum/persistent_client/New(ckey, client)
src.client = client
achievements = new(ckey)
GLOB.persistent_clients_by_ckey[ckey] = src
GLOB.persistent_clients += src
/datum/persistent_client/Destroy(force)
SHOULD_CALL_PARENT(FALSE)
. = QDEL_HINT_LETMELIVE
CRASH("Who the FUCK tried to delete a persistent client? Get your head checked you leadskull.")
/// Setter for the mob var, handles both references.
/datum/persistent_client/proc/set_mob(mob/new_mob)
if(mob == new_mob)
return
mob?.persistent_client = null
new_mob?.persistent_client?.set_mob(null)
mob = new_mob
new_mob?.persistent_client = src
/// Writes all of the `played_names` into an HTML-escaped string.
/datum/player_details/proc/get_played_names()
/datum/persistent_client/proc/get_played_names()
var/list/previous_names = list()
for(var/previous_name in played_names)
previous_names += html_encode("[previous_name] ([played_names[previous_name]])")
return previous_names.Join("; ")
/// Returns the full version string (i.e 515.1642) of the BYOND version and build.
/datum/player_details/proc/full_byond_version()
/datum/persistent_client/proc/full_byond_version()
if(!byond_version)
return "Unknown"
return "[byond_version].[byond_build || "xxx"]"
/// Adds the new names to the player's played_names list on their /datum/player_details for use of admins.
/// Adds the new names to the player's played_names list on their /datum/persistent_client for use of admins.
/// `ckey` should be their ckey, and `data` should be an associative list with the keys being the names they played under and the values being the unique mob ID tied to that name.
/proc/log_played_names(ckey, data)
if(!ckey)
return
var/datum/player_details/writable = GLOB.player_details[ckey]
var/datum/persistent_client/writable = GLOB.persistent_clients_by_ckey[ckey]
if(isnull(writable))
return
@@ -94,7 +94,7 @@
user.dropItemToGround(src)
return
magnification.key = chosen_one.key
magnification.PossessByPlayer(chosen_one.key)
playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE)
to_chat(magnification, span_notice("You're a mind magnified monkey! Protect your helmet with your life- if you lose it, your sentience goes with it!"))
var/policy = get_policy(ROLE_MONKEY_HELMET)
+1 -1
View File
@@ -148,7 +148,7 @@
old_body = observer.mind.current, \
)
new_player.equipOutfit(loadout) // Loadout
new_player.key = ckey
new_player.PossessByPlayer(ckey)
players_info["mob"] = new_player
for(var/datum/deathmatch_modifier/modifier as anything in modifiers)
@@ -71,7 +71,7 @@
var/obj/vent = pick_n_take(vents)
var/mob/dead/observer/selected = pick_n_take(candidates)
var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc)
new_xeno.key = selected.key
new_xeno.PossessByPlayer(selected.key)
new_xeno.move_into_vent(vent)
spawncount--
@@ -51,7 +51,7 @@
return MAP_ERROR
var/mob/living/basic/revenant/revvie = new(pick(spawn_locs))
revvie.key = chosen_one.key
revvie.PossessByPlayer(chosen_one.key)
message_admins("[ADMIN_LOOKUPFLW(revvie)] has been made into a revenant by an event.")
revvie.log_message("was spawned as a revenant by an event.", LOG_GAME)
spawned_mobs += revvie
+1 -1
View File
@@ -87,7 +87,7 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list(
spawned_animals++
selected.key = picked_candidate.key
selected.PossessByPlayer(picked_candidate.key)
selected.grant_all_languages(UNDERSTOOD_LANGUAGE, grant_omnitongue = FALSE, source = LANGUAGE_ATOM)
@@ -26,7 +26,7 @@
if(isnull(spawn_location))
return MAP_ERROR
var/mob/living/basic/space_dragon/dragon = new(spawn_location)
dragon.key = chosen_one.key
dragon.PossessByPlayer(chosen_one.key)
dragon.mind.add_antag_datum(/datum/antagonist/space_dragon)
playsound(dragon, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1)
message_admins("[ADMIN_LOOKUPFLW(dragon)] has been made into a Space Dragon by an event.")
@@ -24,7 +24,7 @@
return NOT_ENOUGH_PLAYERS
//spawn the ninja and assign the candidate
var/mob/living/carbon/human/ninja = create_space_ninja(spawn_location)
ninja.key = chosen_one.key
ninja.PossessByPlayer(chosen_one.key)
ninja.mind.add_antag_datum(/datum/antagonist/ninja)
spawned_mobs += ninja
message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a space ninja by an event.")
+1 -1
View File
@@ -89,6 +89,6 @@
if(isnull(chosen_one))
return NOT_ENOUGH_PLAYERS
santa = new /mob/living/carbon/human(pick(GLOB.blobstart))
santa.key = chosen_one.key
santa.PossessByPlayer(chosen_one.key)
var/datum/antagonist/santa/A = new
santa.mind.add_antag_datum(A)
+1 -1
View File
@@ -23,7 +23,7 @@
I.name = I.dna.real_name
I.updateappearance(mutcolor_update=1)
I.domutcheck()
I.key = chosen_one.key
I.PossessByPlayer(chosen_one.key)
var/datum/antagonist/wizard/master = M.has_antag_datum(/datum/antagonist/wizard)
if(!master.wiz_team)
master.create_wiz_team()
+1 -1
View File
@@ -104,7 +104,7 @@
/obj/item/fish/soul/proc/good_ending(mob/living/user)
var/mob/living/basic/spaceman/soulman = new(get_turf(user))
if(prob(80)) // the percentage is important.
soulman.ckey = user.ckey
soulman.PossessByPlayer(user.ckey)
to_chat(soulman, span_notice("You finally feel at peace."))
user.gib()
qdel(src)
+2 -2
View File
@@ -181,9 +181,9 @@
podman.real_name = "Pod Person ([rand(1,999)])"
mind.transfer_to(podman)
if(ckey)
podman.ckey = ckey
podman.PossessByPlayer(ckey)
else
podman.ckey = ckey_holder
podman.PossessByPlayer(ckey_holder)
podman.gender = blood_gender
podman.faction |= factions
if(!features["mcolor"])
+1 -1
View File
@@ -122,7 +122,7 @@
old_mind = player.mob.mind, \
old_body = player.mob.mind.current, \
)
body.key = player.key
body.PossessByPlayer(player.key)
/**
* Tests kill immunities, if nothing prevents the kill, kills this role.
@@ -456,7 +456,7 @@
using = FALSE
return
soul.ckey = ghost.ckey
soul.PossessByPlayer(ghost.ckey)
soul.copy_languages(master, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the master.
soul.faction = list("[REF(master)]")
balloon_alert(master, "the scythe glows")
@@ -90,14 +90,16 @@
else
to_chat(src, span_notice("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.PossessByPlayer(key)
observer.client = client
observer.set_ghost_appearance()
if(observer.client && observer.client.prefs)
observer.real_name = observer.client.prefs.read_preference(/datum/preference/name/real_name)
observer.name = observer.real_name
observer.client.init_verbs()
observer.client.player_details.time_of_death = world.time
observer.persistent_client.time_of_death = world.time
observer.update_appearance()
observer.stop_sound_channel(CHANNEL_LOBBYMUSIC)
deadchat_broadcast(" has observed.", "<b>[observer.real_name]</b>", follow_target = observer, turf_target = get_turf(observer), message_type = DEADCHAT_DEATHRATTLE)
@@ -151,7 +153,7 @@
/mob/dead/new_player/proc/AttemptLateSpawn(rank)
// Check that they're picking someone new for new character respawning
if(CONFIG_GET(flag/allow_respawn) == RESPAWN_FLAG_NEW_CHARACTER)
if("[client.prefs.default_slot]" in client.player_details.joined_as_slots)
if("[client.prefs.default_slot]" in persistent_client.joined_as_slots)
tgui_alert(usr, "You already have played this character in this round!")
return FALSE
@@ -263,8 +265,9 @@
mind.active = FALSE //we wish to transfer the key manually
var/mob/living/spawning_mob = mind.assigned_role.get_spawn_mob(client, destination)
if(QDELETED(src) || !client)
if(QDELETED(src) || !HAS_CONNECTED_PLAYER(src))
return // Disconnected while checking for the appearance ban.
if(!isAI(spawning_mob)) // Unfortunately there's still snowflake AI code out there.
// transfer_to sets mind to null
var/datum/mind/preserved_mind = mind
@@ -272,7 +275,7 @@
preserved_mind.transfer_to(spawning_mob) //won't transfer key since the mind is not active
preserved_mind.set_original_character(spawning_mob)
LAZYADD(client.player_details.joined_as_slots, "[client.prefs.default_slot]")
LAZYADD(persistent_client.joined_as_slots, "[client.prefs.default_slot]")
client.init_verbs()
. = spawning_mob
new_character = .
@@ -282,7 +285,7 @@
. = new_character
if(!.)
return
new_character.key = key //Manually transfer the key to log them in,
new_character.PossessByPlayer(key) //Manually transfer the key to log them in,
new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC)
var/area/joined_area = get_area(new_character.loc)
if(joined_area)
+4 -4
View File
@@ -283,7 +283,7 @@ Works together with spawning an observer, noted above.
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.key = key
ghost.PossessByPlayer(key)
ghost.client?.init_verbs()
if(!can_reenter_corpse)// Disassociates observer mind from the body mind
ghost.mind = null
@@ -293,7 +293,7 @@ Works together with spawning an observer, noted above.
if(isliving(former_mob))
recordable_time = former_mob.timeofdeath
ghost.client?.player_details.time_of_death = recordable_time
ghost.persistent_client?.time_of_death = recordable_time
SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZED)
return ghost
@@ -386,7 +386,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
SStgui.on_transfer(src, mind.current) // Transfer NanoUIs.
if(mind.current.stat == DEAD && SSlag_switch.measures[DISABLE_DEAD_KEYLOOP])
to_chat(src, span_warning("To leave your body again use the Ghost verb."))
mind.current.key = key
mind.current.PossessByPlayer(key)
mind.current.client.init_verbs()
return TRUE
@@ -680,7 +680,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
to_chat(src, span_warning("Someone has taken this body while you were choosing!"))
return FALSE
target.key = key
target.PossessByPlayer(key)
target.faction = list(FACTION_NEUTRAL)
return TRUE
+1 -1
View File
@@ -719,7 +719,7 @@ GLOBAL_LIST_INIT(command_strings, list(
if(isnull(mind))
mind.transfer_to(paicard.pai)
else
paicard.pai.key = key
paicard.pai.PossessByPlayer(key)
else
ghostize(FALSE) // The pAI card that just got ejected was dead.
@@ -117,7 +117,7 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial())
var/datum/guardian_fluff/guardian_theme = GLOB.guardian_themes[theme]
var/mob/living/basic/guardian/summoned_guardian = new guardian_path(user, guardian_theme)
summoned_guardian.set_summoner(user, different_person = TRUE)
summoned_guardian.key = candidate.key
summoned_guardian.PossessByPlayer(candidate.key)
user.log_message("has summoned [key_name(summoned_guardian)], a [summoned_guardian.creator_name] holoparasite.", LOG_GAME)
summoned_guardian.log_message("was summoned as a [summoned_guardian.creator_name] holoparasite.", LOG_GAME)
to_chat(user, guardian_theme.get_fluff_string(summoned_guardian.guardian_type))
@@ -180,7 +180,7 @@
to_chat(owner, span_boldholoparasite("The personality of <font color=\"[chosen_guardian.guardian_colour]\">[chosen_guardian.theme.name]</font> has been successfully reset."))
message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])")
chosen_guardian.ghostize(FALSE)
chosen_guardian.key = chosen_one.key
chosen_guardian.PossessByPlayer(chosen_one.key)
COOLDOWN_START(chosen_guardian, resetting_cooldown, 5 MINUTES)
chosen_guardian.guardian_rename() //give it a new color and name, to show it's a new person
chosen_guardian.guardian_recolour()
@@ -67,7 +67,7 @@
if(mind)
mind.transfer_to(specter)
else
specter.key = key
specter.PossessByPlayer(key)
return ..()
/mob/living/basic/parrot/poly/get_static_list_of_phrases() // there's only one poly, so there should only be one ongoing list of phrases. i guess
@@ -149,7 +149,7 @@
new_slime.set_combat_mode(TRUE)
if(isnull(mind))
new_slime.key = key
new_slime.PossessByPlayer(key)
else
mind.transfer_to(new_slime)
@@ -71,7 +71,7 @@
var/user_name = old_ckey
if(isnull(revenant.client))
var/mob/potential_user = get_new_user()
revenant.key = potential_user.key
revenant.PossessByPlayer(potential_user.key)
user_name = potential_user.ckey
qdel(potential_user)
+1 -1
View File
@@ -77,7 +77,7 @@
if(brainmob.mind)
brainmob.mind.transfer_to(brain_owner)
else
brain_owner.key = brainmob.key
brain_owner.PossessByPlayer(brainmob.key)
brain_owner.set_suicide(HAS_TRAIT(brainmob, TRAIT_SUICIDED))
+1 -1
View File
@@ -155,7 +155,7 @@ GLOBAL_VAR(posibrain_notify_cooldown)
if(candidate.mind && !isobserver(candidate))
candidate.mind.transfer_to(brainmob)
else
brainmob.ckey = candidate.ckey
brainmob.PossessByPlayer(candidate.ckey)
name = "[initial(name)] ([brainmob.name])"
var/policy = get_policy(ROLE_POSIBRAIN)
if(policy)
@@ -119,7 +119,7 @@
var/atom/xeno_loc = get_turf(owner)
var/mob/living/carbon/alien/larva/new_xeno = new(xeno_loc)
new_xeno.key = ghost.key
new_xeno.PossessByPlayer(ghost.key)
SEND_SOUND(new_xeno, sound('sound/mobs/non-humanoids/hiss/hiss5.ogg',0,0,0,100)) //To get the player's attention
new_xeno.add_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_IMMOBILIZED, TRAIT_NO_TRANSFORM), type) //so we don't move during the bursting animation
new_xeno.SetInvisibility(INVISIBILITY_MAXIMUM, id=type)
+2 -1
View File
@@ -193,6 +193,7 @@
if (client)
client.move_delay = initial(client.move_delay)
client.player_details.time_of_death = timeofdeath
persistent_client?.time_of_death = timeofdeath
return TRUE
+2 -2
View File
@@ -1781,7 +1781,7 @@
// Well, no mmind, guess we should try to move a key over
else if(key)
new_mob.key = key
new_mob.PossessByPlayer(key)
/mob/living/proc/unfry_mob() //Callback proc to tone down spam from multiple sizzling frying oil dipping.
REMOVE_TRAIT(src, TRAIT_OIL_FRIED, "cooking_oil_react")
@@ -2932,7 +2932,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
summoned_guardian.fully_replace_character_name(null, picked_name)
if(picked_color)
summoned_guardian.set_guardian_colour(picked_color)
summoned_guardian.key = guardian_client?.key
summoned_guardian.PossessByPlayer(guardian_client?.key)
guardian_client?.init_verbs()
if(del_mob)
qdel(old_mob)
@@ -1097,7 +1097,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
paicard.pai.PossessByPlayer(key)
else
ghostize(FALSE) // The pAI card that just got ejected was dead.
key = null
@@ -468,7 +468,7 @@
var/be_helper = tgui_alert(usr, "Become a Lightgeist? (Warning, You can no longer be revived!)", "Lightgeist Deployment", list("Yes", "No"))
if((be_helper == "Yes") && !QDELETED(src) && isobserver(user))
var/mob/living/basic/lightgeist/deployable = new(get_turf(loc))
deployable.key = user.key
deployable.PossessByPlayer(user.key)
/obj/machinery/anomalous_crystal/possessor //Allows you to bodyjack small animals, then exit them at your leisure, but you can only do this once per activation. Because they blow up. Also, if the bodyjacked animal dies, SO DO YOU.
observer_desc = "When activated, this crystal allows you to take over small animals, and then exit them at the possessors leisure. Exiting the animal kills it, and if you die while possessing the animal, you die as well."
@@ -206,7 +206,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
visible_message(span_boldwarning("[mychild] emerges from [src]!"))
playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
if(boosted)
mychild.key = elitemind.key
mychild.PossessByPlayer(elitemind.key)
mychild.sentience_act()
notify_ghosts(
"\A [mychild] has been awakened in \the [get_area(src)]!",
+6 -5
View File
@@ -31,6 +31,8 @@
return FALSE
canon_client = client
client.persistent_client.set_mob(src)
add_to_player_list()
lastKnownIP = client.address
computer_id = client.computer_id
@@ -108,13 +110,12 @@
else
client.change_view(getScreenSize(client.prefs.read_preference(/datum/preference/toggle/widescreen)))
if(client.player_details.player_actions.len)
for(var/datum/action/A in client.player_details.player_actions)
A.Grant(src)
for(var/datum/action/A as anything in persistent_client.player_actions)
A.Grant(src)
for(var/foo in client.player_details.post_login_callbacks)
var/datum/callback/CB = foo
for(var/datum/callback/CB as anything in persistent_client.post_login_callbacks)
CB.Invoke()
log_played_names(
client.ckey,
list(
+16 -4
View File
@@ -29,6 +29,8 @@
else if(ckey)
stack_trace("Mob without client but with associated ckey, [ckey], has been deleted.")
persistent_client?.set_mob(null)
remove_from_mob_list()
remove_from_dead_mob_list()
remove_from_alive_mob_list()
@@ -107,6 +109,17 @@
. = ..()
tag = "mob_[next_mob_id++]"
/// Assigns a (c)key to this mob.
/mob/proc/PossessByPlayer(ckey)
SHOULD_NOT_OVERRIDE(TRUE)
if(isnull(ckey))
return
if(!istext(ckey))
CRASH("Tried to assign a mob a non-text ckey, wtf?!")
src.ckey = ckey(ckey)
/mob/serialize_list(list/options, list/semvers)
. = ..()
@@ -780,14 +793,14 @@
qdel(M)
return
M.key = key
M.PossessByPlayer(key)
/// Checks if the mob can respawn yet according to the respawn delay
/mob/proc/check_respawn_delay(override_delay = 0)
if(!override_delay && !CONFIG_GET(number/respawn_delay))
return TRUE
var/death_time = world.time - client.player_details.time_of_death
var/death_time = world.time - persistent_client.time_of_death
var/required_delay = override_delay || CONFIG_GET(number/respawn_delay)
@@ -1535,8 +1548,7 @@
if(!canon_client)
return
for(var/foo in canon_client.player_details.post_logout_callbacks)
var/datum/callback/CB = foo
for(var/datum/callback/CB as anything in persistent_client.post_logout_callbacks)
CB.Invoke()
if(canon_client?.movingmob)
+2
View File
@@ -27,6 +27,8 @@
/// We also need to clear this var/do other cleanup in client/Destroy, since that happens before logout
/// HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
var/client/canon_client
/// It's like a client, but persists! Persistent clients will stick to a mob until the client in question is logged into a different mob.
var/datum/persistent_client/persistent_client
var/shift_to_open_context_menu = TRUE
+1 -1
View File
@@ -376,7 +376,7 @@
to_chat(M, "Your mob has been taken over by a ghost!")
message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(M)])")
M.ghostize(FALSE)
M.key = chosen_one.key
M.PossessByPlayer(chosen_one.key)
M.client?.init_verbs()
return TRUE
else
@@ -67,7 +67,7 @@
mind.transfer_to(desired_mob, 1) // second argument to force key move to new mob
else
desired_mob.key = key
desired_mob.PossessByPlayer(key)
SEND_SIGNAL(src, COMSIG_MOB_CHANGED_TYPE, desired_mob)
if(delete_old_mob)
+8 -8
View File
@@ -155,7 +155,7 @@
mind.active = FALSE
mind.transfer_to(new_borg, TRUE)
else if(transfer_after)
new_borg.key = key
new_borg.PossessByPlayer(key)
if(new_borg.mmi)
new_borg.mmi.name = "[initial(new_borg.mmi.name)]: [real_name]"
@@ -222,7 +222,7 @@
new_xeno = new /mob/living/carbon/alien/adult/drone(loc)
new_xeno.set_combat_mode(TRUE)
new_xeno.key = key
new_xeno.PossessByPlayer(key)
to_chat(new_xeno, span_boldnotice("You are now an alien."))
qdel(src)
@@ -254,7 +254,7 @@
else
new_slime = new /mob/living/basic/slime(loc)
new_slime.set_combat_mode(TRUE)
new_slime.key = key
new_slime.PossessByPlayer(key)
to_chat(new_slime, span_boldnotice("You are now a slime. Skreee!"))
qdel(src)
@@ -262,7 +262,7 @@
/mob/proc/become_overmind(starting_points = OVERMIND_STARTING_POINTS)
var/mob/eye/blob/B = new /mob/eye/blob(get_turf(src), starting_points)
B.key = key
B.PossessByPlayer(key)
. = B
qdel(src)
@@ -282,7 +282,7 @@
var/mob/living/basic/pet/dog/corgi/new_corgi = new /mob/living/basic/pet/dog/corgi (loc)
new_corgi.set_combat_mode(TRUE)
new_corgi.key = key
new_corgi.PossessByPlayer(key)
to_chat(new_corgi, span_boldnotice("You are now a Corgi. Yap Yap!"))
qdel(src)
@@ -333,7 +333,7 @@
if(mind)
mind.transfer_to(new_gorilla)
else
new_gorilla.key = key
new_gorilla.PossessByPlayer(key)
to_chat(new_gorilla, span_boldnotice("You are now a gorilla. Ooga ooga!"))
qdel(src)
return new_gorilla
@@ -365,7 +365,7 @@
var/mob/living/new_mob = new mobpath(src.loc)
new_mob.key = key
new_mob.PossessByPlayer(key)
new_mob.set_combat_mode(TRUE)
to_chat(new_mob, span_boldnotice("You suddenly feel more... animalistic."))
@@ -384,7 +384,7 @@
var/mob/living/new_mob = new mobpath(src.loc)
new_mob.key = key
new_mob.PossessByPlayer(key)
new_mob.set_combat_mode(TRUE)
to_chat(new_mob, span_boldnotice("You feel more... animalistic."))
+1 -1
View File
@@ -242,7 +242,7 @@
if(mob_possessor.mind)
mob_possessor.mind.transfer_to(spawned_mob, force_key_move = TRUE)
else
spawned_mob.key = mob_possessor.key
spawned_mob.PossessByPlayer(mob_possessor.key)
var/datum/mind/spawned_mind = spawned_mob.mind
if(spawned_mind)
spawned_mob.mind.set_assigned_role_with_greeting(SSjob.get_job_type(spawner_job_path))
+1 -1
View File
@@ -212,7 +212,7 @@
var/mob/living/silicon/pai/new_pai = new(src)
new_pai.name = candidate.name || pick(GLOB.ninja_names)
new_pai.real_name = new_pai.name
new_pai.key = candidate.ckey
new_pai.PossessByPlayer(candidate.ckey)
set_personality(new_pai)
SSpai.candidates -= ckey
return TRUE
+2 -2
View File
@@ -24,7 +24,7 @@
pai.name = chosen_name
pai.real_name = pai.name
pai.key = choice.key
pai.PossessByPlayer(choice.key)
card.set_personality(pai)
if(SSpai.candidates[key])
SSpai.candidates -= key
@@ -38,7 +38,7 @@
/mob/proc/make_pai(delete_old)
var/obj/item/pai_card/card = new(src)
var/mob/living/silicon/pai/pai = new(card)
pai.key = key
pai.PossessByPlayer(key)
pai.name = name
card.set_personality(pai)
if(delete_old)
+1 -1
View File
@@ -378,7 +378,7 @@
if(chosen_one)
to_chat(target, span_boldnotice("You have been noticed by a ghost and it has possessed you!"))
var/mob/dead/observer/ghosted_target = target.ghostize(FALSE)
target.key = chosen_one.key
target.PossessByPlayer(chosen_one.key)
trauma.add_friend(ghosted_target)
else
to_chat(target, span_notice("Your mind has managed to go unnoticed in the spirit world."))
@@ -736,7 +736,7 @@
being_used = FALSE
return
dumb_mob.key = ghost.key
dumb_mob.PossessByPlayer(ghost.key)
dumb_mob.mind.enslave_mind_to_creator(user)
SEND_SIGNAL(dumb_mob, COMSIG_SIMPLEMOB_SENTIENCEPOTION, user)
@@ -129,7 +129,7 @@
// Just in case the swappee's key wasn't grabbed by transfer_to...
if(to_swap_key)
caster.key = to_swap_key
caster.PossessByPlayer(to_swap_key)
// MIND TRANSFER END
+1 -1
View File
@@ -3797,7 +3797,7 @@
#include "code\modules\client\client_defines.dm"
#include "code\modules\client\client_procs.dm"
#include "code\modules\client\message.dm"
#include "code\modules\client\player_details.dm"
#include "code\modules\client\persistent_client.dm"
#include "code\modules\client\preferences.dm"
#include "code\modules\client\preferences_menu.dm"
#include "code\modules\client\preferences_savefile.dm"