Skip to content

Commit 1103153

Browse files
authored
Persistent client (#1218)
* simple replacement first * small * fixes * psosessbyplayer * make null safe * tweak * balls * fuck dude im so dumb * fixes
1 parent e4a2c73 commit 1103153

File tree

70 files changed

+215
-150
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+215
-150
lines changed

code/__DEFINES/_helpers.dm

+3
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@
2424
// Custom types that we define don't get a unique id, but this is useful for identifying
2525
// types that don't normally have a way to run istype() on them.
2626
#define TYPEID(thing) copytext(REF(thing), 4, 6)
27+
28+
/// Abstraction over using mob.client to just check if there's a connected player.
29+
#define HAS_CONNECTED_PLAYER(mob) (mob.client)

code/__HELPERS/game.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@
294294

295295
ghost_player.client.prefs.safe_transfer_prefs_to(new_character)
296296
new_character.dna.update_dna_identity()
297-
new_character.key = ghost_player.key
297+
new_character.PossessByPlayer(ghost_player.ckey)
298298

299299
return new_character
300300

code/__HELPERS/roundend.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@
652652

653653
/datum/controller/subsystem/ticker/proc/give_show_report_button(client/C)
654654
var/datum/action/report/R = new
655-
C.player_details.player_actions += R
655+
C.persistent_client.player_actions += R
656656
R.Grant(C.mob)
657657
to_chat(C,"<span class='infoplain'><a href='?src=[REF(R)];report=1'>Show roundend report again</a></span>")
658658

code/controllers/subsystem/achievements.dm

+4-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ SUBSYSTEM_DEF(achievements)
3030

3131
for(var/i in GLOB.clients)
3232
var/client/C = i
33-
if(!C.player_details.achievements.initialized)
34-
C.player_details.achievements.InitializeData()
33+
if(!C.persistent_client.achievements.initialized)
34+
C.persistent_client.achievements.InitializeData()
3535

3636
return ..()
3737

@@ -40,8 +40,8 @@ SUBSYSTEM_DEF(achievements)
4040

4141
/datum/controller/subsystem/achievements/proc/save_achievements_to_db()
4242
var/list/cheevos_to_save = list()
43-
for(var/ckey in GLOB.player_details)
44-
var/datum/player_details/PD = GLOB.player_details[ckey]
43+
for(var/ckey in GLOB.persistent_clients_by_ckey)
44+
var/datum/persistent_client/PD = GLOB.persistent_clients_by_ckey[ckey]
4545
if(!PD || !PD.achievements)
4646
continue
4747
cheevos_to_save += PD.achievements.get_changed_data()

code/controllers/subsystem/blackbox.dm

+2-3
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,8 @@ SUBSYSTEM_DEF(blackbox)
8383
if (MS.rc_msgs.len)
8484
record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console")
8585

86-
for(var/player_key in GLOB.player_details)
87-
var/datum/player_details/PD = GLOB.player_details[player_key]
88-
record_feedback("tally", "client_byond_version", 1, PD.byond_version)
86+
for(var/datum/persistent_client/PC as anything in GLOB.persistent_clients)
87+
record_feedback("tally", "client_byond_version", 1, PC.full_byond_version())
8988

9089
/datum/controller/subsystem/blackbox/Shutdown()
9190
sealed = FALSE

code/controllers/subsystem/vote.dm

+5-5
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ SUBSYSTEM_DEF(vote)
142142
V.Grant(C.mob)
143143
V.name = "Vote: [current_vote.name]"
144144

145-
C.player_details.player_actions += V
145+
C.persistent_client.player_actions += V
146146
generated_actions += V
147147

148148
if(C.prefs.toggles & SOUND_ANNOUNCEMENTS)
@@ -260,11 +260,11 @@ SUBSYSTEM_DEF(vote)
260260

261261
// We also need to remove our action from the player actions when we're cleaning up.
262262
/datum/action/vote/Remove(mob/removed_from)
263-
if(removed_from.client)
264-
removed_from.client?.player_details.player_actions -= src
263+
if(removed_from.persistent_client)
264+
removed_from.persistent_client?.player_actions -= src
265265

266266
else if(removed_from.ckey)
267-
var/datum/player_details/associated_details = GLOB.player_details[removed_from.ckey]
268-
associated_details?.player_actions -= src
267+
var/datum/persistent_client/persistent_client = GLOB.persistent_clients_by_ckey[removed_from.ckey]
268+
persistent_client?.player_actions -= src
269269

270270
return ..()

code/datums/achievements/_achievement_data.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,4 @@
146146
set name = "Check achievements"
147147
set desc = "See all of your achievements!"
148148

149-
player_details.achievements.ui_interact(usr)
149+
persistent_client.achievements.ui_interact(usr)

code/datums/brain_damage/imaginary_friend.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as [owner.real_name]'s imaginary friend?", ROLE_PAI, null, 7.5 SECONDS, friend, POLL_IGNORE_IMAGINARYFRIEND)
5151
if(LAZYLEN(candidates))
5252
var/mob/dead/observer/C = pick(candidates)
53-
friend.key = C.key
53+
friend.PossessByPlayer(C.ckey)
5454
friend_initialized = TRUE
5555
else
5656
qdel(src)

code/datums/brain_damage/split_personality.dm

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as [owner.real_name]'s split personality?", ROLE_PAI, null, 7.5 SECONDS, stranger_backseat, POLL_IGNORE_SPLITPERSONALITY)
3737
if(LAZYLEN(candidates))
3838
var/mob/dead/observer/C = pick(candidates)
39-
stranger_backseat.key = C.key
39+
stranger_backseat.PossessByPlayer(C.ckey)
4040
log_game("[key_name(stranger_backseat)] became [key_name(owner)]'s split personality.")
4141
message_admins("[ADMIN_LOOKUPFLW(stranger_backseat)] became [ADMIN_LOOKUPFLW(owner)]'s split personality.")
4242
else
@@ -92,7 +92,7 @@
9292
owner.computer_id = null
9393
owner.lastKnownIP = null
9494

95-
new_backseat.ckey = owner.ckey
95+
new_backseat.PossessByPlayer(owner.ckey)
9696

9797
new_backseat.name = owner.name
9898

@@ -208,7 +208,7 @@
208208
var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as [owner.real_name]'s brainwashed mind?", null, null, 7.5 SECONDS, stranger_backseat)
209209
if(LAZYLEN(candidates))
210210
var/mob/dead/observer/C = pick(candidates)
211-
stranger_backseat.key = C.key
211+
stranger_backseat.PossessByPlayer(C.ckey)
212212
else
213213
qdel(src)
214214

code/datums/components/spirit_holding.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868

6969
var/mob/dead/observer/chosen_spirit = pick(candidates)
7070
bound_spirit = new(parent)
71-
bound_spirit.ckey = chosen_spirit.ckey
71+
bound_spirit.PossessByPlayer(chosen_spirit.ckey)
7272
bound_spirit.fully_replace_character_name(null, "The spirit of [parent]")
7373
bound_spirit.status_flags |= GODMODE
7474
bound_spirit.copy_languages(awakener, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the awakener.

code/datums/mind.dm

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
- IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you.
2020
2121
- When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting
22-
a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done.
22+
a ghost to become a xeno during an event). Pass the player's key or ckey into the following proc.
2323
24-
new_mob.key = key
24+
new_mob.PossessByPlayer(ckey)
2525
2626
The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple!
2727
However if you want that mind to have any special properties like being a traitor etc you will have to do that
@@ -187,7 +187,7 @@
187187
RegisterSignal(new_character, COMSIG_LIVING_DEATH, PROC_REF(set_death_time))
188188

189189
if(active || force_key_move)
190-
new_character.key = key //now transfer the key to link the client to our new body
190+
new_character.PossessByPlayer(key) //now transfer the key to link the client to our new body
191191

192192
if(new_character.client)
193193
LAZYCLEARLIST(new_character.client.recent_examines)

code/datums/pathogens/transformation.dm

+2-3
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
if(affected_mob.mind)
7474
affected_mob.mind.transfer_to(new_mob)
7575
else
76-
new_mob.key = affected_mob.key
76+
new_mob.PossessByPlayer(affected_mob.ckey)
7777
if(transformed_antag_datum)
7878
new_mob.mind.add_antag_datum(transformed_antag_datum)
7979
new_mob.name = affected_mob.real_name
@@ -89,13 +89,12 @@
8989
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!"))
9090
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.")
9191
affected_mob.ghostize(0)
92-
affected_mob.key = C.key
92+
affected_mob.PossessByPlayer(C.ckey)
9393
else
9494
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!"))
9595
new_mob.death()
9696
if (!QDELETED(new_mob))
9797
new_mob.ghostize(can_reenter_corpse = FALSE)
98-
new_mob.key = null
9998

10099
/datum/pathogen/transformation/jungle_flu
101100
name = "Jungle Flu"

code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm

+2-2
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@
595595
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/generate_ruleset_body(mob/applicant)
596596
var/obj/vent = pick_n_take(vents)
597597
var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc)
598-
new_xeno.key = applicant.key
598+
new_xeno.PossessByPlayer(applicant.ckey)
599599
new_xeno.move_into_vent(vent)
600600
message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.")
601601
log_game("DYNAMIC: [key_name(new_xeno)] was spawned as an alien by the midround ruleset.")
@@ -791,7 +791,7 @@
791791

792792
/datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant)
793793
var/mob/living/simple_animal/revenant/revenant = new(pick(spawn_locs))
794-
revenant.key = applicant.key
794+
revenant.PossessByPlayer(applicant.key)
795795
message_admins("[ADMIN_LOOKUPFLW(revenant)] has been made into a revenant by the midround ruleset.")
796796
log_game("[key_name(revenant)] was spawned as a revenant by the midround ruleset.")
797797
return revenant

code/game/machinery/computer/arena.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
M.set_species(/datum/species/human) // Could use setting per team
189189
M.equipOutfit(outfits[team] ? outfits[team] : default_outfit)
190190
M.faction += team //In case anyone wants to add team based stuff to arena special effects
191-
M.key = ckey
191+
M.PossessByPlayer(ckey)
192192

193193
/obj/machinery/computer/arena/proc/change_outfit(mob/user,team)
194194
outfits[team] = user.client.robust_dress_shop()

code/game/objects/effects/anomalies.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@
354354
var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as a pyroclastic anomaly slime?", ROLE_SENTIENCE, null, 10 SECONDS, S, POLL_IGNORE_PYROSLIME)
355355
if(LAZYLEN(candidates))
356356
var/mob/dead/observer/chosen = pick(candidates)
357-
S.key = chosen.key
357+
S.PossessByPlayer(chosen.ckey)
358358
S.mind.special_role = ROLE_PYROCLASTIC_SLIME
359359
var/policy = get_policy(ROLE_PYROCLASTIC_SLIME)
360360
if (policy)

code/game/objects/items/devices/paicard.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
/// The newly downloaded pAI personality
111111
var/mob/living/silicon/pai/new_pai = new(src)
112112
new_pai.set_real_name(candidate.name || pick(GLOB.ninja_names))
113-
new_pai.key = candidate.key
113+
new_pai.PossessByPlayer(candidate.key)
114114
setPersonality(new_pai)
115115
SSpai.candidates -= candidate
116116
if("fix_speech")

code/modules/admin/admin.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@
241241
log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].")
242242
SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control")
243243

244-
tomob.key = frommob.key
244+
tomob.PossessByPlayer(frommob.key)
245245
tomob.client?.init_verbs()
246246
qdel(frommob)
247247

code/modules/admin/fun_balloon.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595

9696
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(body)])")
9797
body.ghostize(FALSE)
98-
body.key = C.key
98+
body.PossessByPlayer(C.key)
9999
new /obj/effect/temp_visual/gravpush(get_turf(body))
100100

101101
// ----------- Emergency Shuttle Balloon

code/modules/admin/player_panel.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@
282282
var/M_key = html_encode(M.key)
283283
var/previous_names = ""
284284
if(M_key)
285-
var/datum/player_details/P = GLOB.player_details[ckey(M_key)]
285+
var/datum/persistent_client/P = GLOB.persistent_clients_by_ckey[ckey(M_key)]
286286
if(P)
287287
previous_names = P.played_names.Join(",")
288288
previous_names = html_encode(previous_names)

code/modules/admin/smites/imaginary_friend_special.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@
5555
else
5656
friend_mob = new /mob/camera/imaginary_friend(get_turf(target), target, friend_candidate_client.prefs)
5757

58-
friend_mob.key = friend_candidate_client.key
58+
friend_mob.PossessByPlayer(friend_candidate_client.key)

code/modules/admin/verbs/admingame.dm

+2-2
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
213213
var/mob/living/carbon/human/species/monkey/new_monkey = new
214214
SSjob.SendToLateJoin(new_monkey)
215215
G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use
216-
new_monkey.key = G_found.key
216+
new_monkey.PossessByPlayer(G_found.key)
217217
to_chat(new_monkey, "You have been fully respawned. Enjoy the game.", confidential = TRUE)
218218
var/msg = span_adminnotice("[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy monkey.")
219219
message_admins(msg)
@@ -251,7 +251,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
251251
if(is_unassigned_job(new_character.mind.assigned_role))
252252
new_character.mind.set_assigned_role(SSjob.GetJobType(SSjob.overflow_role))
253253

254-
new_character.key = G_found.key
254+
new_character.PossessByPlayer(G_found.key)
255255

256256
/*
257257
The code below functions with the assumption that the mob is already a traitor if they have a special role.

code/modules/admin/verbs/debug.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
return
6262

6363
pai.set_real_name(chosen_name)
64-
pai.key = choice.key
64+
pai.PossessByPlayer(choice.key)
6565
card.setPersonality(pai)
6666
for(var/datum/pai_candidate/candidate in SSpai.candidates)
6767
if(candidate.key == choice.key)

code/modules/admin/verbs/ert.dm

+2-2
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
var/chosen_outfit = usr.client?.prefs?.read_preference(/datum/preference/choiced/brief_outfit)
125125
usr.client.prefs.safe_transfer_prefs_to(admin_officer, is_antag = TRUE)
126126
admin_officer.equipOutfit(chosen_outfit)
127-
admin_officer.key = usr.key
127+
admin_officer.PossessByPlayer(usr.key)
128128
else
129129
to_chat(usr, span_warning("Could not spawn you in as briefing officer as you are not a ghost!"))
130130

@@ -178,7 +178,7 @@
178178
//Spawn the body
179179
var/mob/living/carbon/human/ert_operative = new ertemplate.mobtype(spawnloc)
180180
chosen_candidate.client.prefs.safe_transfer_prefs_to(ert_operative, is_antag = TRUE)
181-
ert_operative.key = chosen_candidate.key
181+
ert_operative.PossessByPlayer(chosen_candidate.key)
182182

183183
if(ertemplate.enforce_human || !(ert_operative.dna.species.changesource_flags & ERT_SPAWN)) // Don't want any exploding plasmemes
184184
ert_operative.set_species(/datum/species/human)

code/modules/admin/verbs/individual_logging.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
var/log_source = M.logging
4949
if(source == LOGSRC_CKEY && M.ckey)
50-
var/datum/player_details/details = GLOB.player_details[M.ckey]
50+
var/datum/persistent_client/details = GLOB.persistent_clients_by_ckey[M.ckey]
5151
if(details) //we dont want to runtime if an admin aghosted
5252
log_source = details.logging
5353
var/list/concatenated_logs = list()

code/modules/admin/verbs/secrets.dm

+2-2
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller)
571571
chosen_candidate = pick(candidates)
572572
candidates -= chosen_candidate
573573
nerd = new /mob/living/simple_animal/drone/classic(spawnpoint)
574-
nerd.key = chosen_candidate.key
574+
nerd.PossessByPlayer(chosen_candidate.key)
575575
log_game("[key_name(nerd)] has been selected as a Nanotrasen emergency response drone")
576576
teamsize--
577577

@@ -609,7 +609,7 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller)
609609
var/mob/chosen = players[1]
610610
if (chosen.client)
611611
chosen.client.prefs.safe_transfer_prefs_to(spawnedMob, is_antag = TRUE)
612-
spawnedMob.key = chosen.key
612+
spawnedMob.PossessByPlayer(chosen.key)
613613
players -= chosen
614614
if (ishuman(spawnedMob) && ispath(humanoutfit, /datum/outfit))
615615
var/mob/living/carbon/human/H = spawnedMob

code/modules/antagonists/_common/antag_datum.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ GLOBAL_LIST_EMPTY(antagonists)
204204
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!")
205205
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner)]) to replace a jobbanned player.")
206206
owner.current.ghostize(0)
207-
owner.current.key = C.key
207+
owner.current.PossessByPlayer(C.key)
208208

209209
/**
210210
* Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion.

code/modules/antagonists/_common/antag_spawner.dm

+3-3
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
new /obj/effect/particle_effect/fluid/smoke(T)
7171
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
7272
C.prefs.safe_transfer_prefs_to(M, is_antag = TRUE)
73-
M.key = C.key
73+
M.PossessByPlayer(C.key)
7474
var/datum/mind/app_mind = M.mind
7575

7676
var/datum/antagonist/wizard/apprentice/app = new()
@@ -216,7 +216,7 @@
216216
borg.mmi.brainmob.set_real_name(brainopsname)
217217
borg.set_real_name(borg.name)
218218

219-
borg.key = C.key
219+
borg.PossessByPlayer(C.key)
220220

221221
var/datum/antagonist/nukeop/new_borg = new()
222222
new_borg.send_to_spawnpoint = FALSE
@@ -264,7 +264,7 @@
264264
var/mob/living/simple_animal/hostile/imp/slaughter/S = new demon_type(T)
265265
new /obj/effect/dummy/phased_mob(T, S)
266266

267-
S.key = C.key
267+
S.PossessByPlayer(C.key)
268268
S.mind.set_assigned_role(SSjob.GetJobType(/datum/job/slaughter_demon))
269269
S.mind.special_role = ROLE_SLAUGHTER_DEMON
270270
S.mind.add_antag_datum(antag_type)

code/modules/antagonists/blob/powers.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@
168168
blobber.adjustHealth(blobber.maxHealth * 0.5)
169169
blob_mobs += blobber
170170
var/mob/dead/observer/C = pick(candidates)
171-
blobber.key = C.key
171+
blobber.PossessByPlayer(C.key)
172172
SEND_SOUND(blobber, sound('sound/effects/blobattack.ogg'))
173173
SEND_SOUND(blobber, sound('sound/effects/attackblob.ogg'))
174174
to_chat(blobber, "<b>You are a blobbernaut!</b>")

code/modules/antagonists/cult/runes.dm

+2-2
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ structure_check() searches for nearby cultist structures required for the invoca
606606
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.")
607607
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.")
608608
mob_to_revive.ghostize(0)
609-
mob_to_revive.key = C.key
609+
mob_to_revive.PossessByPlayer(C.key)
610610
else
611611
fail_invoke()
612612
return
@@ -851,7 +851,7 @@ structure_check() searches for nearby cultist structures required for the invoca
851851
visible_message(span_warning("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo":""]man."))
852852
to_chat(user, span_cultitalic("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..."))
853853
var/obj/structure/emergency_shield/cult/weak/N = new(T)
854-
new_human.key = ghost_to_spawn.key
854+
new_human.PossessByPlayer(ghost_to_spawn.key)
855855
new_human.mind?.add_antag_datum(/datum/antagonist/cult)
856856
to_chat(new_human, span_cultitalic("<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>"))
857857

0 commit comments

Comments
 (0)