/* Note from Carnie:
The way datum/mind stuff works has been changed a lot.
Minds now represent IC characters rather than following a client around constantly.
Guidelines for using minds properly:
- Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living!
ghost.mind is however used as a reference to the ghost's corpse
- When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human)
the existing mind of the old mob should be transfered to the new mob like so:
mind.transfer_to(new_mob)
- You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you.
By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing
the player.
- 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.
new_mob.key = key
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
yourself.
*/
/datum/mind
var/key
var/name //replaces mob/var/original_name
var/ghostname //replaces name for observers name if set
var/mob/living/current
var/active = 0
var/memory
var/assigned_role
var/special_role
var/list/restricted_roles = list()
var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button.
var/linglink
var/datum/martial_art/martial_art
var/static/default_martial_art = new/datum/martial_art
var/miming = FALSE // Mime's vow of silence
var/list/antag_datums
var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state
var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD
var/damnation_type = 0
var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src
var/hasSoul = TRUE // If false, renders the character unable to sell their soul.
var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this.
var/mob/living/enslaved_to //If this mind's master is another mob (i.e. adamantine golems)
var/datum/language_holder/language_holder
var/unconvertable = FALSE
var/late_joiner = FALSE
var/last_death = 0
var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator
var/list/learned_recipes //List of learned recipe TYPES.
///List of skills the user has received a reward for. Should not be used to keep track of currently known skills. Lazy list because it shouldnt be filled often
var/list/skills_rewarded
///Assoc list of skills. Use SKILL_LVL to access level, and SKILL_EXP to access skill's exp.
var/list/known_skills = list()
///What character we spawned in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not
var/mob/original_character
///Skill multiplier, adjusts how much xp you get/loose from adjust_xp. Dont override it directly, add your reason to experience_multiplier_reasons and use that as a key to put your value in there.
var/experience_multiplier = 1
///Skill multiplier list, just slap your multiplier change onto this with the type it is coming from as key.
var/list/experience_multiplier_reasons = list()
/datum/mind/New(key)
src.key = key
soulOwner = src
martial_art = default_martial_art
init_known_skills()
/datum/mind/Destroy()
SSticker.minds -= src
if(islist(antag_datums))
QDEL_LIST(antag_datums)
current = null
soulOwner = null
return ..()
/datum/mind/proc/get_language_holder()
if(!language_holder)
language_holder = new (src)
return language_holder
/datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0)
original_character = null
if(current) // remove ourself from our old body's mind variable
current.mind = null
UnregisterSignal(current, COMSIG_MOB_DEATH)
SStgui.on_transfer(current, new_character)
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.
else
key = new_character.key
if(new_character.mind) //disassociate any mind currently in our new body's mind variable
new_character.mind.current = null
var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list
var/mob/living/old_current = current
if(current)
current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one
current = new_character //associate ourself with our new body
new_character.mind = src //and associate our new body with ourself
for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body
var/datum/antagonist/A = a
A.on_body_transfer(old_current, current)
if(iscarbon(new_character))
var/mob/living/carbon/C = new_character
C.last_mind = src
transfer_antag_huds(hud_to_transfer) //inherit the antag HUD
transfer_actions(new_character)
transfer_martial_arts(new_character)
RegisterSignal(new_character, COMSIG_MOB_DEATH, .proc/set_death_time)
if(active || force_key_move)
new_character.key = key //now transfer the key to link the client to our new body
if(new_character.client)
LAZYCLEARLIST(new_character.client.recent_examines)
current.update_atom_languages()
/datum/mind/proc/init_known_skills()
for (var/type in GLOB.skill_types)
known_skills[type] = list(SKILL_LEVEL_NONE, 0)
///Return the amount of EXP needed to go to the next level. Returns 0 if max level
/datum/mind/proc/exp_needed_to_level_up(skill)
var/lvl = update_skill_level(skill)
if (lvl >= length(SKILL_EXP_LIST)) //If we're already past the last exp threshold
return 0
return SKILL_EXP_LIST[lvl+1] - known_skills[skill][SKILL_EXP]
///Adjust experience of a specific skill
/datum/mind/proc/adjust_experience(skill, amt, silent = FALSE, force_old_level = 0)
var/datum/skill/S = GetSkillRef(skill)
var/old_level = force_old_level ? force_old_level : known_skills[skill][SKILL_LVL] //Get current level of the S skill
experience_multiplier = initial(experience_multiplier)
for(var/key in experience_multiplier_reasons)
experience_multiplier += experience_multiplier_reasons[key]
known_skills[skill][SKILL_EXP] = max(0, known_skills[skill][SKILL_EXP] + amt*experience_multiplier) //Update exp. Prevent going below 0
known_skills[skill][SKILL_LVL] = update_skill_level(skill)//Check what the current skill level is based on that skill's exp
if(silent)
return
if(known_skills[skill][SKILL_LVL] > old_level)
S.level_gained(src, known_skills[skill][SKILL_LVL], old_level)
else if(known_skills[skill][SKILL_LVL] < old_level)
S.level_lost(src, known_skills[skill][SKILL_LVL], old_level)
///Set experience of a specific skill to a number
/datum/mind/proc/set_experience(skill, amt, silent = FALSE)
var/old_level = known_skills[skill][SKILL_EXP]
known_skills[skill][SKILL_EXP] = amt
adjust_experience(skill, 0, silent, old_level) //Make a call to adjust_experience to handle updating level
///Set level of a specific skill
/datum/mind/proc/set_level(skill, newlevel, silent = FALSE)
var/oldlevel = get_skill_level(skill)
var/difference = SKILL_EXP_LIST[newlevel] - SKILL_EXP_LIST[oldlevel]
adjust_experience(skill, difference, silent)
///Check what the current skill level is based on that skill's exp
/datum/mind/proc/update_skill_level(skill)
var/i = 0
for (var/exp in SKILL_EXP_LIST)
i ++
if (known_skills[skill][SKILL_EXP] >= SKILL_EXP_LIST[i])
continue
return i - 1 //Return level based on the last exp requirement that we were greater than
return i //If we had greater EXP than even the last exp threshold, we return the last level
///Gets the skill's singleton and returns the result of its get_skill_modifier
/datum/mind/proc/get_skill_modifier(skill, modifier)
var/datum/skill/S = GetSkillRef(skill)
return S.get_skill_modifier(modifier, known_skills[skill][SKILL_LVL])
///Gets the player's current level number from the relevant skill
/datum/mind/proc/get_skill_level(skill)
return known_skills[skill][SKILL_LVL]
///Gets the player's current exp from the relevant skill
/datum/mind/proc/get_skill_exp(skill)
return known_skills[skill][SKILL_EXP]
/datum/mind/proc/get_skill_level_name(skill)
var/level = get_skill_level(skill)
return SSskills.level_names[level]
/datum/mind/proc/print_levels(user)
var/list/shown_skills = list()
for(var/i in known_skills)
if(known_skills[i][SKILL_LVL] > SKILL_LEVEL_NONE) //Do we actually have a level in this?
shown_skills += i
if(!length(shown_skills))
to_chat(user, "You don't seem to have any particularly outstanding skills.")
return
var/msg = "*---------*\nYour skills\n"
for(var/i in shown_skills)
var/datum/skill/the_skill = i
msg += "[initial(the_skill.name)] - [get_skill_level_name(the_skill)]\n"
msg += ""
to_chat(user, msg)
/datum/mind/proc/set_death_time()
last_death = world.time
/datum/mind/proc/store_memory(new_text)
var/newlength = length_char(memory) + length_char(new_text)
if (newlength > MAX_MESSAGE_LEN * 100)
memory = copytext_char(memory, -newlength-MAX_MESSAGE_LEN * 100)
memory += "[new_text]
"
/datum/mind/proc/wipe_memory()
memory = null
// Datum antag mind procs
/datum/mind/proc/add_antag_datum(datum_type_or_instance, team)
if(!datum_type_or_instance)
return
var/datum/antagonist/A
if(!ispath(datum_type_or_instance))
A = datum_type_or_instance
if(!istype(A))
return
else
A = new datum_type_or_instance()
//Choose snowflake variation if antagonist handles it
var/datum/antagonist/S = A.specialization(src)
if(S && S != A)
qdel(A)
A = S
if(!A.can_be_owned(src))
qdel(A)
return
A.owner = src
LAZYADD(antag_datums, A)
A.create_team(team)
var/datum/team/antag_team = A.get_team()
if(antag_team)
antag_team.add_member(src)
A.on_gain()
log_game("[key_name(src)] has gained antag datum [A.name]([A.type])")
return A
/datum/mind/proc/remove_antag_datum(datum_type)
if(!datum_type)
return
var/datum/antagonist/A = has_antag_datum(datum_type)
if(A)
A.on_removal()
return TRUE
/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us.
for(var/a in antag_datums)
var/datum/antagonist/A = a
A.on_removal()
/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE)
if(!datum_type)
return
. = FALSE
for(var/a in antag_datums)
var/datum/antagonist/A = a
if(check_subtypes && istype(A, datum_type))
return A
else if(A.type == datum_type)
return A
/*
Removes antag type's references from a mind.
objectives, uplinks, powers etc are all handled.
*/
/datum/mind/proc/remove_changeling()
var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling)
if(C)
remove_antag_datum(/datum/antagonist/changeling)
special_role = null
/datum/mind/proc/remove_traitor()
remove_antag_datum(/datum/antagonist/traitor)
/datum/mind/proc/remove_brother()
if(src in SSticker.mode.brothers)
remove_antag_datum(/datum/antagonist/brother)
/datum/mind/proc/remove_nukeop()
var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE)
if(nuke)
remove_antag_datum(nuke.type)
special_role = null
/datum/mind/proc/remove_wizard()
remove_antag_datum(/datum/antagonist/wizard)
special_role = null
/datum/mind/proc/remove_cultist()
if(src in SSticker.mode.cult)
SSticker.mode.remove_cultist(src, 0, 0)
special_role = null
remove_antag_equip()
/datum/mind/proc/remove_rev()
var/datum/antagonist/rev/rev = has_antag_datum(/datum/antagonist/rev)
if(rev)
remove_antag_datum(rev.type)
special_role = null
/datum/mind/proc/remove_antag_equip()
var/list/Mob_Contents = current.get_contents()
for(var/obj/item/I in Mob_Contents)
var/datum/component/uplink/O = I.GetComponent(/datum/component/uplink) //Todo make this reset signal
if(O)
O.unlock_code = null
/datum/mind/proc/remove_all_antag() //For the Lazy amongst us.
remove_changeling()
remove_traitor()
remove_nukeop()
remove_wizard()
remove_cultist()
remove_rev()
/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner)
if(!current)
return
var/mob/living/carbon/human/traitor_mob = current
if (!istype(traitor_mob))
return
var/list/all_contents = traitor_mob.GetAllContents()
var/obj/item/pda/PDA = locate() in all_contents
var/obj/item/radio/R = locate() in all_contents
var/obj/item/pen/P
if (PDA) // Prioritize PDA pen, otherwise the pocket protector pens will be chosen, which causes numerous ahelps about missing uplink
P = locate() in PDA
if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way
P = locate() in all_contents
var/obj/item/uplink_loc
if(traitor_mob.client && traitor_mob.client.prefs)
switch(traitor_mob.client.prefs.uplink_spawn_loc)
if(UPLINK_PDA)
uplink_loc = PDA
if(!uplink_loc)
uplink_loc = R
if(!uplink_loc)
uplink_loc = P
if(UPLINK_RADIO)
uplink_loc = R
if(!uplink_loc)
uplink_loc = PDA
if(!uplink_loc)
uplink_loc = P
if(UPLINK_PEN)
uplink_loc = P
if(!uplink_loc) // We've looked everywhere, let's just give you a pen
if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack!
P = new /obj/item/pen(traitor_mob.back)
else
P = new /obj/item/pen(traitor_mob.loc)
traitor_mob.put_in_hands(P) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused.
uplink_loc = P
if (!uplink_loc)
if(!silent)
to_chat(traitor_mob, "Unfortunately, [employer] wasn't able to get you an Uplink.")
. = 0
else
. = uplink_loc
var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key)
if(!U)
CRASH("Uplink creation failed.")
U.setup_unlock_code()
if(!silent)
if(uplink_loc == R)
to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.")
else if(uplink_loc == PDA)
to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.")
else if(uplink_loc == P)
to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [english_list(U.unlock_code)] from its starting position to unlock its hidden features.")
if(uplink_owner)
uplink_owner.antag_memory += U.unlock_note + "
"
else
traitor_mob.mind.store_memory(U.unlock_note)
//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does.
/datum/mind/proc/enslave_mind_to_creator(mob/living/creator)
if(iscultist(creator))
SSticker.mode.add_cultist(src)
else if(is_revolutionary(creator))
var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE)
converter.add_revolutionary(src,FALSE)
else if(is_nuclear_operative(creator))
var/datum/antagonist/nukeop/converter = creator.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)
var/datum/antagonist/nukeop/N = new()
N.send_to_spawnpoint = FALSE
N.nukeop_outfit = null
add_antag_datum(N,converter.nuke_team)
enslaved_to = creator
current.faction |= creator.faction
creator.faction |= current.faction
if(creator.mind.special_role)
message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.")
to_chat(current, "Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.")
/datum/mind/proc/show_memory(mob/recipient, window=1)
if(!recipient)
recipient = current
var/output = "[current.real_name]'s Memories:
"
output += memory
var/list/all_objectives = list()
for(var/datum/antagonist/A in antag_datums)
output += A.antag_memory
all_objectives |= A.objectives
if(all_objectives.len)
output += "Objectives:"
var/obj_count = 1
for(var/datum/objective/objective in all_objectives)
output += "
Objective #[obj_count++]: [objective.explanation_text]"
var/list/datum/mind/other_owners = objective.get_owners() - src
if(other_owners.len)
output += "