mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-26 09:03:37 +00:00
## About The Pull Request Implements https://hackmd.io/@tgstation/SkeUS7lSp , rewriting Dynamic from the ground-up - Dynamic configuration is now vastly streamlined, making it far far far easier to understand and edit - Threat is gone entirely; round chaos is now determined by dynamic tiers - There's 5 dynamic tiers, 0 to 4. - 0 is a pure greenshift. - Tiers are just picked via weight - "16% chance of getting a high chaos round". - Tiers have min pop ranges. "Tier 4 (high chaos) requires 25 pop to be selected". - Tier determines how much of every ruleset is picked. "Tier 4 (High Chaos) will pick 3-4 roundstart[1], 1-2 light, 1-2 heavy, and 2-3 latejoins". - The number of rulesets picked depends on how many people are in the server - this is also configurable[2]. As an example, a tier that demands "1-3" rulesets will not spawn 3 rulesets if population <= 40 and will not spawn 2 rulesets if population <= 25. - Tiers also determine time before light, heavy, and latejoin rulesets are picked, as well as the cooldown range between spawns. More chaotic tiers may send midrounds sooner or wait less time between sending them. - On the ruleset side of things, "requirements", "scaling", and "enemies" is gone. - You can configure a ruleset's min pop and weight flat, or per tier. - For example a ruleset like Obsession is weighted higher for tiers 1-2 and lower for tiers 3-4. - Rather than scaling up, roundstart rulesets can just be selected multiple times. - Rulesets also have `min_antag_cap` and `max_antag_cap`. `min_antag_cap` determines how many candidates are needed for it to run, and `max_antag_cap` determines how many candidates are selected. - Rulesets attempt to run every 2.5 minutes. [3] - Light rulesets will ALWAYS be picked before heavy rulesets. [4] - Light injection chance is no longer 100%, heavy injection chance formula has been simplified. - Chance simply scales based on number of dead players / total number off players, with a flag 50% chance if no antags exist. [5] [1] This does not guarantee you will actually GET 3-4 roundstart rulesets. If a roundstart ruleset is picked, and it ends up being unable to execute (such as "not enough candidates", that slot is effectively a wash.) This might be revisited. [2] Currently, this is a hard limit - below X pop, you WILL get a quarter or a half of the rulesets. This might be revisited to just be weighted - you are just MORE LIKELY to get a quarter or a half. [3] Little worried about accidentally frontloading everything so we'll see about this [4] This may be revisited but in most contexts it seems sensible. [5] This may also be revisited, I'm not 100% sure what the best / most simple way to tackle midround chances is. Other implementation details - The process of making rulesets has been streamlined as well. Many rulesets only amount to a definition and `assign_role`. - Dynamic.json -> Dynamic.toml - Dynamic event hijacked was ripped out entirely. - Most midround antag random events are now dynamic rulesets. Fugitives, Morphs, Slaughter Demons, etc. - The 1 weight slaughter demon event is gone. RIP in peace. - There is now a hidden midround event that simply adds +1 latejoin, +1 light, or +1 heavy ruleset. - `mind.special_role` is dead. Minds have a lazylist of special roles now but it's essentially only used for traitor panel. - Revs refactored almost entirely. Revs can now exist without a dynamic ruleset. - Cult refactored a tiny bit. - Antag datums cleaned up. - Pre round setup is less centralized on Dynamic. - Admins have a whole panel for interfacing with dynamic. It's pretty slapdash I'm sure someone could make a nicer looking one.   - Maybe some other things. ## Why It's Good For The Game See readme for more info. Will you see a massive change in how rounds play out? My hunch says rounds will spawn less rulesets on average, but it's ultimately to how it's configured ## Changelog 🆑 Melbert refactor: Dynamic rewritten entirely, report any strange rounds config: Dynamic config reworked, it's now a TOML file refactor: Refactored antag roles somewhat, report any oddities refactor: Refactored Revolution entirely, report any oddities del: Deleted most midround events that spawn antags - they use dynamic rulesets now add: Dynamic rulesets can now be false alarms add: Adds a random event that gives dynamic the ability to run another ruleset later admin: Adds a panel for messing around with dynamic admin: Adds a panel for chance for every dynamic ruleset to be selected admin: You can spawn revs without using dynamic now fix: Nuke team leaders get their fun title back /🆑
534 lines
21 KiB
Plaintext
534 lines
21 KiB
Plaintext
/* 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 transferred 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
|
|
/// Key of the mob
|
|
var/key
|
|
/// The name linked to this mind
|
|
var/name
|
|
/// replaces name for observers name if set
|
|
var/ghostname
|
|
/// Current mob this mind datum is attached to
|
|
var/mob/living/current
|
|
/// Is this mind active?
|
|
var/active = FALSE
|
|
|
|
/// a list of /datum/memories. assoc type of memory = memory datum. only one type of memory will be stored, new ones of the same type overriding the last.
|
|
var/list/memories = list()
|
|
/// reference to the memory panel tgui
|
|
var/datum/memory_panel/memory_panel
|
|
|
|
/// Job datum indicating the mind's role. This should always exist after initialization, as a reference to a singleton.
|
|
var/datum/job/assigned_role
|
|
|
|
/// List of antag datums on this mind
|
|
var/list/antag_datums
|
|
/// this mind's ANTAG_HUD should have this icon_state
|
|
var/antag_hud_icon_state = null
|
|
///this mind's antag HUD
|
|
var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud = null
|
|
var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this.
|
|
|
|
///If this mind's master is another mob (i.e. adamantine golems). Weakref of a /living.
|
|
var/datum/weakref/enslaved_to
|
|
|
|
var/late_joiner = FALSE
|
|
/// has this mind ever been an AI
|
|
var/has_ever_been_ai = FALSE
|
|
var/last_death = 0
|
|
|
|
/// Set by Into The Sunset command of the shuttle manipulator.
|
|
/// If TRUE, the mob will always be considered "escaped" if they are alive and not exiled.
|
|
var/force_escaped = FALSE
|
|
|
|
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()
|
|
///Weakref to thecharacter we joined in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not
|
|
var/datum/weakref/original_character
|
|
/// The index for what character slot, if any, we were loaded from, so we can track persistent scars on a per-character basis. Each character slot gets PERSISTENT_SCAR_SLOTS scar slots
|
|
var/original_character_slot_index
|
|
/// The index for our current scar slot, so we don't have to constantly check the savefile (unlike the slots themselves, this index is independent of selected char slot, and increments whenever a valid char is joined with)
|
|
var/current_scar_slot_index
|
|
|
|
///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()
|
|
|
|
/// A lazy list of roles to display that this mind has, stuff like "Traitor" or "Special Creature"
|
|
var/list/special_roles
|
|
/// A lazy list of statuses to display that this mind has, stuff like "Infected" or "Mindshielded"
|
|
var/list/special_statuses
|
|
|
|
///Assoc list of addiction values, key is the type of withdrawal (as singleton type), and the value is the amount of addiction points (as number)
|
|
var/list/addiction_points
|
|
///Assoc list of key active addictions and value amount of cycles that it has been active.
|
|
var/list/active_addictions
|
|
///List of objective-specific equipment that couldn't properly be given to the mind
|
|
var/list/failed_special_equipment
|
|
/// A list to keep track of which books a person has read (to prevent people from reading the same book again and again for positive mood events)
|
|
var/list/book_titles_read
|
|
|
|
/datum/mind/New(_key)
|
|
key = _key
|
|
init_known_skills()
|
|
set_assigned_role(SSjob.get_job_type(/datum/job/unassigned)) // Unassigned by default.
|
|
|
|
/datum/mind/Destroy()
|
|
SSticker.minds -= src
|
|
QDEL_NULL(antag_hud)
|
|
QDEL_LIST_ASSOC_VAL(memories)
|
|
QDEL_NULL(memory_panel)
|
|
QDEL_LIST(antag_datums)
|
|
set_current(null)
|
|
return ..()
|
|
|
|
/datum/mind/serialize_list(list/options, list/semvers)
|
|
. = ..()
|
|
|
|
.["key"] = key
|
|
.["name"] = name
|
|
.["ghostname"] = ghostname
|
|
.["memories"] = memories
|
|
.["antag_datums"] = antag_datums
|
|
.["holy_role"] = holy_role
|
|
.["special_role"] = jointext(get_special_roles(), " | ")
|
|
.["assigned_role"] = assigned_role.title
|
|
.["current"] = current
|
|
|
|
var/mob/enslaved_to = src.enslaved_to?.resolve()
|
|
.["enslaved_to"] = enslaved_to
|
|
|
|
SET_SERIALIZATION_SEMVER(semvers, "1.0.0")
|
|
return .
|
|
|
|
/datum/mind/vv_edit_var(var_name, var_value)
|
|
switch(var_name)
|
|
if(NAMEOF(src, assigned_role))
|
|
set_assigned_role(var_value)
|
|
. = TRUE
|
|
if(NAMEOF(src, holy_role))
|
|
set_holy_role(var_value)
|
|
. = TRUE
|
|
if(!isnull(.))
|
|
datum_flags |= DF_VAR_EDITED
|
|
return
|
|
return ..()
|
|
|
|
|
|
/datum/mind/proc/set_current(mob/new_current)
|
|
if(new_current && QDELETED(new_current))
|
|
CRASH("Tried to set a mind's current var to a qdeleted mob, what the fuck")
|
|
if(current)
|
|
UnregisterSignal(src, COMSIG_QDELETING)
|
|
current = new_current
|
|
if(current)
|
|
RegisterSignal(src, COMSIG_QDELETING, PROC_REF(clear_current))
|
|
|
|
/datum/mind/proc/clear_current(datum/source)
|
|
SIGNAL_HANDLER
|
|
set_current(null)
|
|
|
|
/datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0)
|
|
set_original_character(null)
|
|
if(current) // remove ourself from our old body's mind variable
|
|
current.mind = null
|
|
UnregisterSignal(current, COMSIG_LIVING_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(TRUE) //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.set_current(null)
|
|
|
|
var/mob/living/old_current = current
|
|
if(old_current)
|
|
//transfer anyone observing the old character to the new one
|
|
old_current.transfer_observers_to(new_character)
|
|
|
|
// Offload all mind languages from the old holder to a temp one
|
|
var/datum/language_holder/empty/temp_holder = new()
|
|
var/datum/language_holder/old_holder = old_current.get_language_holder()
|
|
var/datum/language_holder/new_holder = new_character.get_language_holder()
|
|
// Off load mind languages to the temp holder momentarily
|
|
new_holder.transfer_mind_languages(temp_holder)
|
|
// Transfer the old holder's mind languages to the new holder
|
|
old_holder.transfer_mind_languages(new_holder)
|
|
// And finally transfer the temp holder's mind languages back to the old holder
|
|
temp_holder.transfer_mind_languages(old_holder)
|
|
|
|
set_current(new_character) //associate ourself with our new body
|
|
QDEL_NULL(antag_hud)
|
|
new_character.mind = src //and associate our new body with ourself
|
|
antag_hud = new_character.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", src)
|
|
for(var/datum/antagonist/antag_datum as anything in antag_datums) //Makes sure all antag datums effects are applied in the new body
|
|
antag_datum.on_body_transfer(old_current, current)
|
|
if(iscarbon(new_character))
|
|
var/mob/living/carbon/carbon_character = new_character
|
|
carbon_character.last_mind = src
|
|
|
|
RegisterSignal(new_character, COMSIG_LIVING_DEATH, PROC_REF(set_death_time))
|
|
if(active || force_key_move)
|
|
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
|
|
|
|
SEND_SIGNAL(src, COMSIG_MIND_TRANSFERRED, old_current)
|
|
SEND_SIGNAL(current, COMSIG_MOB_MIND_TRANSFERRED_INTO, old_current)
|
|
if(!isnull(old_current))
|
|
SEND_SIGNAL(old_current, COMSIG_MOB_MIND_TRANSFERRED_OUT_OF, current)
|
|
|
|
//I cannot trust you fucks to do this properly
|
|
/datum/mind/proc/set_original_character(new_original_character)
|
|
original_character = WEAKREF(new_original_character)
|
|
|
|
/datum/mind/proc/set_death_time()
|
|
SIGNAL_HANDLER
|
|
|
|
last_death = world.time
|
|
|
|
/datum/mind/Topic(href, href_list)
|
|
if(!check_rights(R_ADMIN))
|
|
return
|
|
|
|
var/self_antagging = usr == current
|
|
|
|
if(href_list["add_antag"])
|
|
add_antag_wrapper(text2path(href_list["add_antag"]),usr)
|
|
|
|
if(href_list["remove_antag"])
|
|
var/datum/antagonist/A = locate(href_list["remove_antag"]) in antag_datums
|
|
if(!istype(A))
|
|
to_chat(usr,span_warning("Invalid antagonist ref to be removed."))
|
|
return
|
|
A.admin_remove(usr)
|
|
|
|
if(href_list["open_antag_vv"])
|
|
var/datum/antagonist/to_vv = locate(href_list["open_antag_vv"]) in antag_datums
|
|
if(!istype(to_vv))
|
|
to_chat(usr, span_warning("Invalid antagonist ref to be vv'd."))
|
|
return
|
|
usr.client?.debug_variables(to_vv)
|
|
|
|
if (href_list["role_edit"])
|
|
var/new_role = input("Select new role", "Assigned role", assigned_role.title) as null|anything in sort_list(SSjob.name_occupations)
|
|
if(isnull(new_role))
|
|
return
|
|
var/datum/job/new_job = SSjob.get_job(new_role)
|
|
if (!new_job)
|
|
to_chat(usr, span_warning("Job not found."))
|
|
return
|
|
set_assigned_role(new_job)
|
|
|
|
else if (href_list["obj_edit"] || href_list["obj_add"])
|
|
var/objective_pos //Edited objectives need to keep same order in antag objective list
|
|
var/def_value
|
|
var/datum/antagonist/target_antag
|
|
var/datum/objective/old_objective //The old objective we're replacing/editing
|
|
var/datum/objective/new_objective //New objective we're be adding
|
|
|
|
if(href_list["obj_edit"])
|
|
for(var/datum/antagonist/A in antag_datums)
|
|
old_objective = locate(href_list["obj_edit"]) in A.objectives
|
|
if(old_objective)
|
|
target_antag = A
|
|
objective_pos = A.objectives.Find(old_objective)
|
|
break
|
|
if(!old_objective)
|
|
to_chat(usr,"Invalid objective.")
|
|
return
|
|
else
|
|
if(href_list["target_antag"])
|
|
var/datum/antagonist/X = locate(href_list["target_antag"]) in antag_datums
|
|
if(X)
|
|
target_antag = X
|
|
if(!target_antag)
|
|
switch(antag_datums.len)
|
|
if(0)
|
|
target_antag = add_antag_datum(/datum/antagonist/custom)
|
|
if(1)
|
|
target_antag = antag_datums[1]
|
|
else
|
|
var/datum/antagonist/target = input("Which antagonist gets the objective:", "Antagonist", "(new custom antag)") as null|anything in sort_list(antag_datums) + "(new custom antag)"
|
|
if (QDELETED(target))
|
|
return
|
|
else if(target == "(new custom antag)")
|
|
target_antag = add_antag_datum(/datum/antagonist/custom)
|
|
else
|
|
target_antag = target
|
|
|
|
if(!GLOB.admin_objective_list)
|
|
generate_admin_objective_list()
|
|
|
|
if(old_objective)
|
|
if(old_objective.name in GLOB.admin_objective_list)
|
|
def_value = old_objective.name
|
|
|
|
var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list
|
|
selected_type = GLOB.admin_objective_list[selected_type]
|
|
if (!selected_type)
|
|
return
|
|
|
|
if(!old_objective)
|
|
//Add new one
|
|
new_objective = new selected_type
|
|
new_objective.owner = src
|
|
new_objective.admin_edit(usr)
|
|
target_antag.objectives += new_objective
|
|
message_admins("[key_name_admin(usr)] added a new objective for [current]: [new_objective.explanation_text]")
|
|
log_admin("[key_name(usr)] added a new objective for [current]: [new_objective.explanation_text]")
|
|
else
|
|
if(old_objective.type == selected_type)
|
|
//Edit the old
|
|
old_objective.admin_edit(usr)
|
|
new_objective = old_objective
|
|
else
|
|
//Replace the old
|
|
new_objective = new selected_type
|
|
new_objective.owner = src
|
|
new_objective.admin_edit(usr)
|
|
target_antag.objectives -= old_objective
|
|
target_antag.objectives.Insert(objective_pos, new_objective)
|
|
message_admins("[key_name_admin(usr)] edited [current]'s objective to [new_objective.explanation_text]")
|
|
log_admin("[key_name(usr)] edited [current]'s objective to [new_objective.explanation_text]")
|
|
|
|
else if (href_list["obj_delete"])
|
|
var/datum/objective/objective
|
|
for(var/datum/antagonist/A in antag_datums)
|
|
objective = locate(href_list["obj_delete"]) in A.objectives
|
|
if(istype(objective))
|
|
A.objectives -= objective
|
|
break
|
|
if(!objective)
|
|
to_chat(usr,"Invalid objective.")
|
|
return
|
|
//qdel(objective) Needs cleaning objective destroys
|
|
message_admins("[key_name_admin(usr)] removed an objective for [current]: [objective.explanation_text]")
|
|
log_admin("[key_name(usr)] removed an objective for [current]: [objective.explanation_text]")
|
|
|
|
else if(href_list["obj_completed"])
|
|
var/datum/objective/objective
|
|
for(var/datum/antagonist/A in antag_datums)
|
|
objective = locate(href_list["obj_completed"]) in A.objectives
|
|
if(istype(objective))
|
|
objective = objective
|
|
break
|
|
if(!objective)
|
|
to_chat(usr,"Invalid objective.")
|
|
return
|
|
objective.completed = !objective.completed
|
|
log_admin("[key_name(usr)] toggled the win state for [current]'s objective: [objective.explanation_text]")
|
|
|
|
else if(href_list["obj_prompt_custom"])
|
|
var/datum/antagonist/target_antag
|
|
if(href_list["target_antag"])
|
|
var/datum/antagonist/found_datum = locate(href_list["target_antag"]) in antag_datums
|
|
if(found_datum)
|
|
target_antag = found_datum
|
|
if(isnull(target_antag))
|
|
switch(length(antag_datums))
|
|
if(0)
|
|
target_antag = add_antag_datum(/datum/antagonist/custom)
|
|
if(1)
|
|
target_antag = antag_datums[1]
|
|
else
|
|
var/datum/antagonist/target = input("Which antagonist gets the objective:", "Antagonist", "(new custom antag)") as null|anything in sort_list(antag_datums) + "(new custom antag)"
|
|
if (QDELETED(target))
|
|
return
|
|
else if(target == "(new custom antag)")
|
|
target_antag = add_antag_datum(/datum/antagonist/custom)
|
|
else
|
|
target_antag = target
|
|
var/replace_existing = input("Replace existing objectives?","Replace objectives?") in list("Yes", "No")
|
|
if (isnull(replace_existing))
|
|
return
|
|
replace_existing = replace_existing == "Yes"
|
|
var/replace_escape
|
|
if (!replace_existing)
|
|
replace_escape = FALSE
|
|
else
|
|
replace_escape = input("Replace survive/escape/martyr objectives?","Replace objectives?") in list("Yes", "No")
|
|
if (isnull(replace_escape))
|
|
return
|
|
replace_escape = replace_escape == "Yes"
|
|
target_antag.submit_player_objective(retain_existing = !replace_existing, retain_escape = !replace_escape, force = TRUE)
|
|
log_admin("[key_name(usr)] prompted [current] to enter their own objectives for [target_antag].")
|
|
|
|
else if (href_list["silicon"])
|
|
switch(href_list["silicon"])
|
|
if("unemag")
|
|
var/mob/living/silicon/robot/R = current
|
|
if (istype(R))
|
|
R.SetEmagged(0)
|
|
message_admins("[key_name_admin(usr)] has unemag'ed [R].")
|
|
log_admin("[key_name(usr)] has unemag'ed [R].")
|
|
|
|
if("unemagcyborgs")
|
|
if(isAI(current))
|
|
var/mob/living/silicon/ai/ai = current
|
|
for (var/mob/living/silicon/robot/R in ai.connected_robots)
|
|
R.SetEmagged(0)
|
|
message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.")
|
|
log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.")
|
|
|
|
else if (href_list["common"])
|
|
switch(href_list["common"])
|
|
if("undress")
|
|
for(var/obj/item/W in current)
|
|
current.dropItemToGround(W, TRUE) //The TRUE forces all items to drop, since this is an admin undress.
|
|
if("takeuplink")
|
|
take_uplink()
|
|
wipe_memory_type(/datum/memory/key/traitor_uplink/implant)
|
|
log_admin("[key_name(usr)] removed [current]'s uplink.")
|
|
if("crystals")
|
|
if(check_rights(R_FUN))
|
|
var/datum/component/uplink/U = find_syndicate_uplink()
|
|
if(U)
|
|
var/crystals = tgui_input_number(
|
|
user = usr,
|
|
message = "Amount of telecrystals for [key]",
|
|
title = "Syndicate uplink",
|
|
default = U.uplink_handler.telecrystals,
|
|
)
|
|
if(isnum(crystals))
|
|
U.uplink_handler.set_telecrystals(crystals)
|
|
message_admins("[key_name_admin(usr)] changed [current]'s telecrystal count to [crystals].")
|
|
log_admin("[key_name(usr)] changed [current]'s telecrystal count to [crystals].")
|
|
if("progression")
|
|
if(!check_rights(R_FUN))
|
|
return
|
|
var/datum/component/uplink/uplink = find_syndicate_uplink()
|
|
if(!uplink)
|
|
return
|
|
var/progression = input("Set new progression points for [key]","Syndicate uplink", uplink.uplink_handler.progression_points) as null | num
|
|
if(isnull(progression))
|
|
return
|
|
uplink.uplink_handler.progression_points = progression
|
|
message_admins("[key_name_admin(usr)] changed [current]'s progression point count to [progression].")
|
|
log_admin("[key_name(usr)] changed [current]'s progression point count to [progression].")
|
|
if("uplink")
|
|
var/datum/antagonist/traitor/traitor_datum = has_antag_datum(/datum/antagonist/traitor)
|
|
if(!give_uplink(antag_datum = traitor_datum || null))
|
|
to_chat(usr, span_danger("Equipping a syndicate failed!"))
|
|
log_admin("[key_name(usr)] tried and failed to give [current] an uplink.")
|
|
else
|
|
log_admin("[key_name(usr)] gave [current] an uplink.")
|
|
|
|
else if (href_list["obj_announce"])
|
|
announce_objectives()
|
|
|
|
//Something in here might have changed your mob
|
|
if(self_antagging && (!usr || !usr.client) && current.client)
|
|
usr = current
|
|
traitor_panel()
|
|
|
|
|
|
/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients)
|
|
for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list))
|
|
if(G.mind == src)
|
|
if(G.can_reenter_corpse || even_if_they_cant_reenter)
|
|
return G
|
|
break
|
|
|
|
/datum/mind/proc/grab_ghost(force)
|
|
var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force)
|
|
. = G
|
|
if(G)
|
|
G.reenter_corpse()
|
|
|
|
///Adds addiction points to the specified addiction
|
|
/datum/mind/proc/add_addiction_points(type, amount)
|
|
LAZYSET(addiction_points, type, min(LAZYACCESS(addiction_points, type) + amount, MAX_ADDICTION_POINTS))
|
|
var/datum/addiction/affected_addiction = SSaddiction.all_addictions[type]
|
|
return affected_addiction.on_gain_addiction_points(src)
|
|
|
|
///Adds addiction points to the specified addiction
|
|
/datum/mind/proc/remove_addiction_points(type, amount)
|
|
LAZYSET(addiction_points, type, max(LAZYACCESS(addiction_points, type) - amount, 0))
|
|
var/datum/addiction/affected_addiction = SSaddiction.all_addictions[type]
|
|
return affected_addiction.on_lose_addiction_points(src)
|
|
|
|
/// Setter for the assigned_role job datum.
|
|
/datum/mind/proc/set_assigned_role(datum/job/new_role)
|
|
if(assigned_role == new_role)
|
|
return
|
|
if(!is_job(new_role))
|
|
CRASH("set_assigned_role called with invalid role: [isnull(new_role) ? "null" : new_role]")
|
|
. = assigned_role
|
|
assigned_role = new_role
|
|
|
|
///Sets your holy role, giving/taking away traits related to if you're gaining/losing it.
|
|
/datum/mind/proc/set_holy_role(new_holy_role)
|
|
if(holy_role == new_holy_role)
|
|
return
|
|
var/was_holy = holy_role
|
|
holy_role = new_holy_role
|
|
if(holy_role)
|
|
ADD_TRAIT(src, TRAIT_SEE_BLESSED_TILES, HOLY_TRAIT)
|
|
else
|
|
REMOVE_TRAIT(src, TRAIT_SEE_BLESSED_TILES, HOLY_TRAIT)
|
|
SEND_SIGNAL(current, COMSIG_MOB_MIND_SET_HOLY_ROLE, new_holy_role)
|
|
//the signal stops tracking when losing holy roles, but since we're gaining it, give us our HUDs if we're becoming holy.
|
|
if(!was_holy && holy_role)
|
|
for(var/datum/atom_hud/alternate_appearance/basic/blessed_aware/blessed_hud in GLOB.active_alternate_appearances)
|
|
blessed_hud.check_hud(current)
|
|
|
|
/// Sets us to the passed job datum, then greets them to their new job.
|
|
/// Use this one for when you're assigning this mind to a new job for the first time,
|
|
/// or for when someone's receiving a job they'd really want to be greeted to.
|
|
/datum/mind/proc/set_assigned_role_with_greeting(datum/job/new_role, client/incoming_client)
|
|
. = set_assigned_role(new_role)
|
|
if(assigned_role != new_role)
|
|
return
|
|
|
|
var/intro_message = new_role.get_spawn_message()
|
|
if(incoming_client && intro_message)
|
|
to_chat(incoming_client, intro_message)
|
|
|
|
/mob/proc/sync_mind()
|
|
mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist)
|
|
mind.active = TRUE //indicates that the mind is currently synced with a client
|
|
|
|
/mob/dead/new_player/sync_mind()
|
|
return
|
|
|
|
/mob/dead/observer/sync_mind()
|
|
return
|