Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new midround antag: the divergent clone #37334

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions __DEFINES/_macros.dm
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@

#define istimeagent(H) (H.mind && (H.mind.GetRole(TIMEAGENT) || (H.mind.GetRole(TIMEAGENTTWIN))))

#define isdivergentclone(H) (H.mind && (H.mind.GetRole(DIVERGENTCLONE)))

#define isERT(H) (H.mind && H.mind.GetRole(RESPONDER))

#define isclownling(H) (H.mind && H.mind.GetRole(CLOWN_LING))
Expand Down
1 change: 1 addition & 0 deletions __DEFINES/role_datums_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
#define JUDGE "judge"
#define GRUE "grue"
#define NANOTRASENOFFICIAL "nanotrasen official"
#define DIVERGENTCLONE "divergent clone"

#define GREET_DEFAULT "default"
#define GREET_ROUNDSTART "roundstart"
Expand Down
81 changes: 81 additions & 0 deletions code/datums/gamemode/dynamic/dynamic_rulesets_midround.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1095,3 +1095,84 @@
requirements = list(10,10,10,10,10,10,10,10,10,10)
logo = "gun-logo"
repeatable = TRUE

//////////////////////////////////////////////
// //
// DIVERGENT CLONE //
// //
//////////////////////////////////////////////

/datum/dynamic_ruleset/midround/from_ghosts/divergentclone
name = "Divergent Clone"
role_category = /datum/role/divergentclone
required_candidates = 1
weight = 3
weight_category = "Clone"
cost = 5
requirements = list(40,30,20,10,10,10,10,10,10,10)
high_population_requirement = 10
logo = "divergentclone-logo"
repeatable = TRUE
makeBody = FALSE
flags = MINOR_RULESET

/datum/dynamic_ruleset/midround/from_ghosts/divergentclone/trim_candidates()
..()
for(var/mob/M in dead_players)
if(isdivergentclone(M))
dead_players -= M
for(var/mob/M in list_observers)
if(isdivergentclone(M))
list_observers -= M


/datum/dynamic_ruleset/midround/from_ghosts/divergentclone/ready(var/forced = 0)
if(!config.revival_cloning)
return 0

//Check that we have at least one ghost who isn't already a clone waiting to spawn
var/list/candies = dead_players + list_observers
var/list/valids[0]
for(var/mob/dead/observer/G in candies)
if(isdivergentclone(G))
continue
valids += G
if(valids.len == 0)
if(forced)
message_admins("Tried to force divergent clone, but no valid candidates found.")
return 0

var/list/clonepods = list()
for(var/obj/machinery/cloning/clonepod/pod in machines)
//Check that the pod has cloned something before
if(pod.cloned_records.len)
clonepods += pod
if(!forced && clonepods.len == 0)
return 0
return ..()

/datum/dynamic_ruleset/midround/from_ghosts/divergentclone/finish_setup(mob/new_character, index)
//Create a new dummy body to create a new mind for the ghost.
var/L = get_turf(new_character)
var/mob/living/carbon/human/H = new /mob/living/carbon/human(pick(latejoin))
H.ckey = new_character.ckey
H.fully_replace_character_name(null, "\improper spirit of a divergent clone")

//Give the about-to-be-a-ghost mob the provisional role and objective
var/datum/role/divergentclone/new_role = new /datum/role/divergentclone(H.mind, override=TRUE)
new_role.ForgeObjectives()
new_role.AnnounceObjectives()
Comment on lines +1163 to +1164
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These procs should fire under /datum/role/divergentclone/New() instead of being called here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree that might make sense, it also seems that no other role does this, so I chose not to do it here either. I'm assuming it's to allow admins to add the role to people without it instantly creating objectives or announcing the objectives to them.


var/mob/dead/observer/G = H.ghostize(FALSE)
//Give it a more spirity looking icon.
G.icon = initial(G.icon)
G.icon_state = initial(G.icon_state)
QDEL_NULL(H)
G.forceMove(L)
G.add_spell(new /spell/targeted/ghost/divergentclone)

to_chat(G, "<span class='notice'><b>You were selected to be a divergent clone, but will not be spawned in yet!</b></span>")
to_chat(G, "<span class='notice'>You have been granted the \"Spawn as Divergent Clone\" ghost spell. Use this near a cloning pod to spawn in as a divergent clone of someone who was cloned, or is currently being cloned, in that pod.</span>")
to_chat(G, "<span class='notice'>Using this spell on an unoccupied cloning pod will allow you to choose a record of a person previously cloned in that pod. Using it on an occupied pod will cause you to become a twin of the person currently in the pod, and be ejected from the pod at the same time as them.</span>")
to_chat(G, "<span class='notice'>Remember: you can only use this spell once, and re-entering your corpse will remove it permanently. In fact, for your convenience we have removed your ability to re-enter your corpse.</span>")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this - I haven't gotten to the code for this yet but if this role is randomly assigned to someone do they immediately lose the ability to re-enter their corpse? That could be a huge bummer for someone who is having a fun round and wants to be revived while keeping the option open for this role. Maybe make it so that they lose the ability to enter their corpse if they accept this role when prompted?

Copy link
Contributor Author

@brndd brndd Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to best handle this, but I think the way I'm doing it already is the least worst option. Like, you lose the ability to re-enter your corpse if you get auto-accepted as a ninja or catbeast. The intent here is the same. Allowing someone to reincarnate after they get this role would mean that the role is wasted, and there aren't good frameworks in place in dynamic to monitor if this happens so that threat could be refunded or the role could be passed to the next person (the only solution that doesn't involve massive amounts of new code would be an infinite loop that runs until the ghost either spawns as a divergent clone or gets resurrected and checks their status every time, and I don't like that).


2 changes: 1 addition & 1 deletion code/datums/gamemode/factions/bloodcult/bloodcult.dm
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@
if (cult_reminders.len)
to_chat(R.antag.current, "<span class='notice'>Other cultists have shared some of their knowledge. It will be stored in your memory (check your Notes under the IC tab).</span>")
for (var/reminder in cult_reminders)
R.antag.store_memory("Shared Cultist Knowledge: [reminder].")
R.antag.store_memory("Shared Cultist Knowledge: [reminder].", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)
previously_converted |= R.antag
if (R.antag.name in deconverted)
deconverted -= R.antag.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@
if (iscultist(M.current))//failsafe for cultist brains put in MMIs
to_chat(M.current, "<span class='game say'><b>[user.real_name]</b>'s voice echoes in your head, <B><span class='sinisterbig'>[reminder]</span></span>")
to_chat(M.current, "<span class='notice'>This message will be remembered by all current cultists, and by new converts as well.</span>")
M.store_memory("Cult reminder: [text].")
M.store_memory("Cult reminder: [text].", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)

for(var/mob/living/simple_animal/astral_projection/A in astral_projections)
to_chat(A, "<span class='game say'><b>[user.real_name]</b> communicates, <span class='sinisterbig'>[reminder]</span></span>. (Cult reminder)")
Expand Down
2 changes: 1 addition & 1 deletion code/datums/gamemode/factions/legacy_cult/cult.dm
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ var/global/list/rnwords = list("ire","ego","nahlizet","certum","veri","jatkaa","
word=pick(allwords)
var/wordexp = "[cult_words[word]] is [word]..."
to_chat(cult_mob, "<span class='sinister'>You remember one thing from the dark teachings of your master... [wordexp]</span>")
cult_mob.mind.store_memory("<B>You remember that</B> [wordexp]", 0, 0)
cult_mob.mind.store_memory("<B>You remember that</B> [wordexp]", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)

/datum/faction/cult/narsie/AdminPanelEntry()
var/list/dat = ..()
Expand Down
3 changes: 1 addition & 2 deletions code/datums/gamemode/factions/plague_mice.dm
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@

/* With the disease set-up, store the detials of the disease in the mouse's memory */
var/datum/mind/mouse_mind = R.antag
mouse_mind.store_memory(plague.get_info(TRUE), forced = 1)
mouse_mind.store_memory("<hr>", forced = 1)
mouse_mind.store_memory(plague.get_info(TRUE), category=MIND_MEMORY_ANTAGONIST, forced = 1)
var/dat = "<span class='notice'>You carry a deadly plague with the following traits:</span>"
dat += "<br><span class='notice'>Strength / Robustness:</span> <b>[plague.strength]%</b> / <b>[plague.robustness]%</b>"
dat += "<br><span class='notice'>Infection chance:</span> <b>[plague.infectionchance]%</b>"
Expand Down
2 changes: 1 addition & 1 deletion code/datums/gamemode/factions/syndicate/nukeops.dm
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
nuke_name_assign(nuke_last_name(synd_mind.current), title, members) //Allows time for the rest of the syndies to be chosen

if(nuke_code)
synd_mind.store_memory("<B>Syndicate Nuclear Bomb Code</B>: [nuke_code]", 0, 0)
synd_mind.store_memory("<B>Syndicate Nuclear Bomb Code</B>: [nuke_code]", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)
to_chat(synd_mind.current, "The nuclear authorization code is: <B>[nuke_code]</B><br>Make sure to share it with your subordinates.")
var/obj/item/weapon/paper/P = new
P.info = "The nuclear authorization code is: <b>[nuke_code]</b>"
Expand Down
2 changes: 1 addition & 1 deletion code/datums/gamemode/factions/vox_shoal.dm
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ var/list/potential_bonus_items = list(
var/datum/outfit/striketeam/voxraider/concrete_outfit = new
concrete_outfit.equip(vox)
vox.regenerate_icons()
vox.store_memory("The priority items for the day are: [english_list(bonus_items_of_the_day)]")
vox.mind.store_memory("The priority items for the day are: [english_list(bonus_items_of_the_day)]", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)

/*
spawn()
Expand Down
6 changes: 3 additions & 3 deletions code/datums/gamemode/misc_gamemode_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
if(!apprentice)
to_chat(wizard_mob, "You will find a list of available spells in your spell book. Choose your magic arsenal carefully.")
to_chat(wizard_mob, "In your pockets you will find a teleport scroll. Use it as needed.")
wizard_mob.mind.store_memory("<B>Remember:</B> do not forget to prepare your spells.")
wizard_mob.mind.store_memory("<B>Remember:</B> do not forget to prepare your spells.", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)
return 1

/proc/name_wizard(mob/living/carbon/human/wizard_mob, role_name = "Space Wizard")
Expand Down Expand Up @@ -346,13 +346,13 @@
if (syndicate_code_phrase)
var/phrases = syndicate_code_phrase.Join(", ")
words += "<span class='warning'>Code Phrases: </span>[phrases].<br>"
agent.mind.store_memory("<b>Code Phrases</b>: [phrases].")
agent.mind.store_memory("<b>Code Phrases</b>: [phrases].", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)
else
words += "Unfortunately, the Syndicate did not provide you with a code phrase.<br>"
if (syndicate_code_response)
var/response = syndicate_code_response.Join(", ")
words += "<span class='warning'>Code Response: </span>[response].<br>"
agent.mind.store_memory("<b>Code Response</b>: [response].")
agent.mind.store_memory("<b>Code Response</b>: [response].", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)
else
words += "Unfortunately, the Syndicate did not provide you with a code response.<br>"

Expand Down
63 changes: 63 additions & 0 deletions code/datums/gamemode/objectives/divergentclone.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/datum/objective/freeform/divergentclone_neutral
explanation_text = "Convince the world that you are the real <person>, or at least as real as the other copy."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What did he mean by this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly just a placeholder/sanity thing. The actual text gets set in PostAppend.


/datum/objective/freeform/divergentclone_neutral/format_explanation()
return "Convince the world that you are the real [owner.name], or at least as real as the other copy."

/datum/objective/freeform/divergentclone_neutral/PostAppend()
explanation_text = format_explanation()
return TRUE

/datum/objective/freeform/divergentclone_evil
explanation_text = "Convince the world that you are the real <person> at any cost, be that by talk, violence or subterfuge. Do not let anyone get in your way, including your original copy."

/datum/objective/freeform/divergentclone_evil/format_explanation()
return "Convince the world that you are the real [owner.name] at any cost, be that by talk, violence or subterfuge. Do not let anyone get in your way, including your original copy."

/datum/objective/freeform/divergentclone_evil/PostAppend()
explanation_text = format_explanation()
return TRUE

/datum/objective/divergentclone/spawn_in
explanation_text = "Reincarnate as a divergent clone."
name = "Reincarnate"

/datum/objective/divergentclone/spawn_in/IsFulfilled()
if(..())
return TRUE
if(!owner || !owner.current)
return FALSE

var/datum/role/divergentclone/role = owner.GetRole(DIVERGENTCLONE)
if(role?.has_spawned_in)
return TRUE

/datum/objective/acquire_personal_id
explanation_text = "Acquire an ID card matching your name or DNA."
name = "Acquire personal ID card"

/datum/objective/acquire_personal_id/IsFulfilled()
if(..())
return TRUE

if(!owner || !owner.current)
return FALSE

for(var/obj/O in get_contents_in_object(owner.current))
var/obj/item/weapon/card/id/I
if(istype(O, /obj/item/weapon/card/id))
I = O
else if(istype(O, /obj/item/device/pda))
var/obj/item/device/pda/P = O
I = P.id
else
continue
var/datum/dna/D = owner.current.dna
if((I?.dna_hash == D.unique_enzymes) || (I?.registered_name == owner.name))
return TRUE

return FALSE




2 changes: 1 addition & 1 deletion code/datums/gamemode/powers/powers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
if (granttext)
to_chat(R.antag.current, "<span class = 'notice'>[granttext]</span>")
if (store_in_memory)
R.antag.store_memory("<font size = '1'>[granttext]</font>")
R.antag.store_memory("<font size = '1'>[granttext]</font>", category=MIND_MEMORY_ANTAGONIST, forced=TRUE)
R.current_powers += src
role = R
grant_spell()
Expand Down
3 changes: 1 addition & 2 deletions code/datums/gamemode/role/catbeast.dm
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ var/list/catbeast_names = list("Meowth","Fluffy","Subject 246","Experiment 35a",
D1.origin = "Loose Catbeast"
D1.makerandom(str, rob, anti, bad)
H.infect_disease2(D1, 1, "Loose Catbeast")
antag.store_memory(D1.get_info(TRUE), forced = 1)
antag.store_memory("<hr>")
antag.store_memory(D1.get_info(TRUE), category=MIND_MEMORY_ANTAGONIST, forced=TRUE)

/datum/role/catbeast/proc/infect_catbeast_tier1(mob/living/carbon/human/H)
var/list/anti = list(
Expand Down
Loading
Loading