Adds a 1/5 chance for Paradox Clone to spawn two for the price of one (#92855)

## About The Pull Request

Paradox Clone ruleset has a 1/5 chance of spawning an additional clone
of the same target. This chance is configurable (so server operators can
disable it), but the amount of clones it adds is not.

The second clone is *not* on a team with the first. Paradox Clone's
primary objective has been updated to `Be the only [x] alive`, rather
than `Kill [x]`.

- [ ] I tested this pr

## Why It's Good For The Game

A while back someone suggested "What if Paradox Clone spawned a third
clone with the objective to protect the guy instead of kill them"

I thought this sounded pretty neat but the idea of spawning an antag
that worked against antags seemed to be grounds for problems. So I stole
half the idea.

This adds a little more variety in Paradox Clone happenings:
- Clones have no idea or indication if there are other clones (unless
you spawn together - which is not guaranteed), so each clone is less
sure of things and has to improvise more
- Neither the clones nor the crew can be fully complacent after dealing
with one "clone"
- More plausible deniability for Genetics or Changelings to mess with
people
- And yes, even the clones aren't 100% sure if the *other* clone is real
or fake, so now they're roped in on the fun

## Changelog

🆑 Melbert
add: There's a 1/5 chance that Paradox Clone spawns a second clone if
there are enough candidates. Neither clone is aware of or on a team with
the other - There can only be one.
add: Paradox Clones are now tasked to `Be the only [x] alive`, meaning
means of identity cloning (such as Genetics or Changelings) may throw a
wrench in your plan.
/🆑
This commit is contained in:
MrMelbert
2025-09-12 16:44:11 +02:00
committed by GitHub
parent 733acfb085
commit da9eb945fc
2 changed files with 35 additions and 34 deletions
@@ -678,21 +678,28 @@
min_pop = 10
max_antag_cap = 1
signup_atom_appearance = /obj/effect/bluespace_stream
/// Chance of getting another clone for the price of free
var/bonus_clone_chance = 20
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/New(list/dynamic_config)
. = ..()
max_antag_cap += prob(bonus_clone_chance)
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/can_be_selected()
return ..() && !isnull(find_clone()) && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE))
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/create_execute_args()
return list(find_clone())
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/create_ruleset_body()
return // handled by assign_role() entirely
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/assign_role(datum/mind/candidate)
var/mob/living/carbon/human/good_version = find_clone()
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/assign_role(datum/mind/candidate, mob/living/carbon/human/good_version)
var/mob/living/carbon/human/bad_version = good_version.make_full_human_copy(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE))
candidate.transfer_to(bad_version, force_key_move = TRUE)
var/datum/antagonist/paradox_clone/antag = candidate.add_antag_datum(/datum/antagonist/paradox_clone)
antag.original_ref = WEAKREF(good_version.mind)
antag.setup_clone()
antag.setup_clone(good_version.mind)
playsound(bad_version, 'sound/items/weapons/zapbang.ogg', 30, TRUE)
bad_version.put_in_hands(new /obj/item/storage/toolbox/mechanical()) //so they dont get stuck in maints
@@ -8,9 +8,6 @@
suicide_cry = "THERE CAN BE ONLY ONE!!"
preview_outfit = /datum/outfit/paradox_clone
///Weakref to the mind of the original, the clone's target.
var/datum/weakref/original_ref
/datum/antagonist/paradox_clone/get_preview_icon()
var/icon/final_icon = render_preview_outfit(preview_outfit)
@@ -28,27 +25,27 @@
return clone_icon
/datum/antagonist/paradox_clone/Destroy()
original_ref = null
return ..()
/datum/antagonist/paradox_clone/proc/setup_clone(datum/mind/original_mind)
if(isnull(original_mind))
CRASH("Tried to set up a paradox clone without an original mind!")
/datum/antagonist/paradox_clone/proc/setup_clone()
var/datum/mind/original_mind = original_ref?.resolve()
var/datum/objective/assassinate/paradox_clone/kill = new
var/datum/objective/accept_no_substitutes/kill = new()
kill.owner = owner
kill.target = original_mind
kill.update_explanation_text()
kill.set_target_name(original_mind.name || "Unknown")
objectives += kill
var/datum/objective/paradox_clone_replace/replace = new()
replace.owner = owner
replace.name_and_job = "[original_mind.name], the [original_mind.assigned_role.title]"
replace.update_explanation_text()
objectives += replace
owner.set_assigned_role(SSjob.get_job_type(/datum/job/paradox_clone))
//clone doesnt show up on message lists
var/obj/item/modular_computer/pda/messenger = locate() in owner.current
if(messenger)
var/datum/computer_file/program/messenger/message_app = locate() in messenger.stored_files
if(message_app)
message_app.invisible = TRUE
var/datum/computer_file/program/messenger/message_app = locate() in messenger?.stored_files
message_app?.invisible = TRUE
//dont want anyone noticing there's two now
var/mob/living/carbon/human/clone_human = owner.current
@@ -64,6 +61,17 @@
/datum/antagonist/paradox_clone/roundend_report_header()
return span_header("A paradox clone appeared on the station!<br>")
/datum/objective/paradox_clone_replace
name = "clone replace"
/// Name and job of the original to replace
var/name_and_job
/datum/objective/paradox_clone_replace/update_explanation_text()
explanation_text = "Take [name_and_job || "someone's"]'s place. Avoid collateral damage - remember, your mission is to blend in!"
/datum/objective/paradox_clone_replace/check_completion()
return completed || considered_alive(owner)
/datum/outfit/paradox_clone
name = "Paradox Clone (Preview only)"
@@ -71,20 +79,6 @@
gloves = /obj/item/clothing/gloves/color/black
head = /obj/item/clothing/head/soft/purple
/**
* Paradox clone assassinate objective
* Similar to the original, but with a different flavortext.
*/
/datum/objective/assassinate/paradox_clone
name = "clone assassinate"
/datum/objective/assassinate/paradox_clone/update_explanation_text()
. = ..()
if(!target?.current)
explanation_text = "Free Objective"
CRASH("WARNING! [ADMIN_LOOKUPFLW(owner)] paradox clone objectives forged without an original!")
explanation_text = "Murder and replace [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())]. Remember, your mission is to blend in, do not kill anyone else unless you have to!"
///Static bluespace stream used in its ghost poll icon.
/obj/effect/bluespace_stream
name = "bluespace stream"