Skip to content

Gives teratomas the captain's car keys (uncommon teratoma spawn event + other misc teratoma stuff) #5989

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

Merged
merged 12 commits into from
Apr 12, 2025
Merged
2 changes: 2 additions & 0 deletions code/__DEFINES/role_preferences.dm
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#define ROLE_FLORIDA_MAN "Florida Man"
#define ROLE_SLASHER "Slasher"
#define ROLE_MONSTERHUNTER "Monster Hunter"
#define ROLE_TERATOMA "Teratoma"

// Latejoin roles
#define ROLE_HERETIC_SMUGGLER "Heretic Smuggler"
Expand Down Expand Up @@ -167,6 +168,7 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_SLASHER = 0,
ROLE_FLORIDA_MAN = 0,
ROLE_OPFOR_CANDIDATE = 0,
ROLE_TERATOMA = 0,
//monkestation edit end

// Latejoin
Expand Down
6 changes: 4 additions & 2 deletions code/modules/client/preferences/middleware/antags.dm
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,13 @@
ROLE_FUGITIVE = /datum/antagonist/fugitive,
ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone,
ROLE_SENTIENCE = /datum/antagonist/sentient_creature,
//monkestation antags
// monkestation start: non-dynamic antags
ROLE_CORTICAL_BORER = /datum/antagonist/cortical_borer,
ROLE_DRIFTING_CONTRACTOR = /datum/antagonist/traitor/contractor,
ROLE_SLASHER = /datum/antagonist/slasher,
ROLE_FLORIDA_MAN = /datum/antagonist/florida_man
ROLE_FLORIDA_MAN = /datum/antagonist/florida_man,
ROLE_TERATOMA = /datum/antagonist/teratoma,
// monkestation end
)

var/list/antagonists = non_ruleset_antagonists.Copy()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
playsound(user.loc, 'sound/effects/blobattack.ogg', 50, 1)
user.spawn_gibs()
user.visible_message(span_danger("Something horrible bursts out of [user]'s chest!"), \
span_danger("Living teratoma bursts out of your chest!"), \
span_danger("A living teratoma bursts out of your chest!"), \
span_hear("You hear flesh tearing!"), COMBAT_MESSAGE_RANGE)
return FALSE //create_teratoma() handles the chemicals anyway so there is no reason to take them again

Expand All @@ -23,10 +23,12 @@
return FALSE
ling.adjust_chemicals(-chemical_cost)
var/list/candidates = SSpolling.poll_ghost_candidates(
"Do you want to play as a living teratoma?",
question = "Do you want to play as a living teratoma?",
role = ROLE_TERATOMA,
poll_time = 7.5 SECONDS,
ignore_category = POLL_IGNORE_TERATOMA,
alert_pic = /datum/antagonist/teratoma,
jump_target = user,
role_name_text = "living teratoma",
chat_text_border_icon = /datum/antagonist/teratoma,
)
Expand All @@ -39,11 +41,12 @@
to_chat(user, span_warning("You fail at creating a tumor. Perhaps you should try again later?"))
ling.adjust_chemicals(chemical_cost)
return FALSE

var/datum/mind/goober_mind = new(candidate.key)
goober_mind.active = TRUE
var/mob/living/carbon/human/species/teratoma/goober = new(user.drop_location())
goober.key = candidate.key
if(!goober.mind)
goober.mind_initialize()
goober.mind.add_antag_datum(/datum/antagonist/teratoma)
goober_mind.transfer_to(goober)
goober_mind.add_antag_datum(/datum/antagonist/teratoma)
to_chat(goober, span_notice("You burst out from [user]'s chest!"))
SEND_SOUND(goober, sound('sound/effects/blobattack.ogg'))
return TRUE
93 changes: 80 additions & 13 deletions monkestation/code/modules/antagonists/teratoma/teratoma.dm
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/// Maximum amount of random ion laws to give teratoma silicons.
#define MAX_TERATOMA_ION_LAWS 3
/// Minimum time between law corruptions, if the teratoma is borged/AI'd.
#define LAW_CORRUPTION_COOLDOWN_MIN (1.5 MINUTES)
/// Maximum time between law corruptions, if the teratoma is borged/AI'd.
#define LAW_CORRUPTION_COOLDOWN_MAX (7.5 MINUTES)

/datum/team/teratoma
name = "Teratomas"
member_name = "teratoma"
Expand All @@ -7,24 +14,84 @@
show_in_antagpanel = TRUE
antagpanel_category = ANTAG_GROUP_ABOMINATIONS
show_to_ghosts = TRUE
suicide_cry = "FOR CHAOS!!"
preview_outfit = /datum/outfit/teratoma
/// The teratoma team. Used solely to combine all teratomas on the roundend report.
var/datum/team/teratoma/team
/// Cooldown for corrupting silicon laws, if someone makes the mistake of turning a teratoma into a borg or AI.
COOLDOWN_DECLARE(corrupt_laws_cooldown)

/datum/antagonist/teratoma/on_gain()
. = ..()
ADD_TRAIT(owner, TRAIT_UNCONVERTABLE, REF(src))
owner.special_role = ROLE_TERATOMA
ADD_TRAIT(owner, TRAIT_UNCONVERTABLE, type)

/datum/antagonist/teratoma/on_removal()
REMOVE_TRAIT(owner, TRAIT_UNCONVERTABLE, REF(src))
STOP_PROCESSING(SSprocessing, src)
REMOVE_TRAIT(owner, TRAIT_UNCONVERTABLE, type)
owner.special_role = null
return ..()

/datum/antagonist/teratoma/apply_innate_effects(mob/living/mob_override)
. = ..()
var/mob/living/our_mob = mob_override || owner.current
ADD_TRAIT(our_mob, TRAIT_EVIL, type)
if(issilicon(our_mob))
corrupt_silicon_laws(our_mob)
START_PROCESSING(SSprocessing, src)
else
STOP_PROCESSING(SSprocessing, src)
if(istype(owner.current?.loc, /obj/item/mmi))
RegisterSignal(our_mob.loc, COMSIG_ATOM_EXAMINE, PROC_REF(on_mmi_examine))

/datum/antagonist/teratoma/remove_innate_effects(mob/living/mob_override)
. = ..()
var/mob/living/our_mob = mob_override || owner.current
REMOVE_TRAIT(our_mob, TRAIT_EVIL, type)
UnregisterSignal(our_mob.loc, COMSIG_ATOM_EXAMINE)

/datum/antagonist/teratoma/proc/on_mmi_examine(datum/source, mob/user, list/examine_text)
SIGNAL_HANDLER
if(!istype(owner.current?.loc, /obj/item/mmi) || source != owner.current.loc)
UnregisterSignal(source, COMSIG_ATOM_EXAMINE) // we got moved out of the MMI, just unregister the signal
return
. += span_warning("There is a small red warning light blinking on it.")

/datum/antagonist/teratoma/process(seconds_per_tick)
if(QDELETED(src) || QDELETED(owner?.current))
return PROCESS_KILL
corrupt_silicon_laws()

/datum/antagonist/teratoma/proc/corrupt_silicon_laws(mob/living/silicon/robotoma)
if(!COOLDOWN_FINISHED(src, corrupt_laws_cooldown))
return
robotoma ||= owner.current
if(!issilicon(robotoma) || QDELING(robotoma))
return
if(iscyborg(robotoma))
var/mob/living/silicon/robot/borgtoma = robotoma
borgtoma.scrambledcodes = TRUE
if(!borgtoma.shell)
borgtoma.lawupdate = FALSE
borgtoma.set_connected_ai(null)
robotoma.clear_inherent_laws(announce = FALSE)
robotoma.clear_supplied_laws(announce = FALSE)
robotoma.clear_ion_laws(announce = FALSE)
robotoma.clear_hacked_laws(announce = FALSE)
robotoma.clear_zeroth_law(force = FALSE, announce = FALSE)
for(var/i = 1 to rand(1, MAX_TERATOMA_ION_LAWS))
robotoma.add_ion_law(generate_ion_law(), announce = FALSE)
robotoma.post_lawchange(announce = TRUE) // NOW show them the message
COOLDOWN_START(src, corrupt_laws_cooldown, rand(LAW_CORRUPTION_COOLDOWN_MIN, LAW_CORRUPTION_COOLDOWN_MAX))

/datum/antagonist/teratoma/greet()
var/list/parts = list()
parts += span_big("You are a living teratoma!")
parts += span_changeling("By all means, you should not exist. <i>Your very existence is a sin against nature itself.</i>")
parts += span_changeling("You are loyal to <b>nobody</b>, except the forces of chaos itself.")
parts += span_info("You are able to easily vault tables and ventcrawl, however you cannot use many things like guns, batons, and you are also illiterate and quite fragile.")
parts += span_hypnophrase("<span style='font-size: 125%'>Spread misery and chaos upon the station.</span>")
to_chat(owner.current, boxed_message(parts.Join("\n")), type = MESSAGE_TYPE_INFO)
to_chat(owner.current, boxed_message(jointext(parts, "\n")), type = MESSAGE_TYPE_INFO)

/datum/antagonist/teratoma/can_be_owned(datum/mind/new_owner)
if(!isteratoma(new_owner.current))
Expand All @@ -46,17 +113,17 @@
/datum/antagonist/teratoma/get_team()
return team

/datum/antagonist/teratoma/get_base_preview_icon()
RETURN_TYPE(/icon)
var/static/icon/teratoma_icon
if(!teratoma_icon)
var/mob/living/carbon/human/species/teratoma/teratoma = new
teratoma.setDir(SOUTH)
teratoma_icon = getFlatIcon(teratoma, no_anim = TRUE)
QDEL_NULL(teratoma)
return teratoma_icon

/datum/objective/teratoma
name = "Spread misery and chaos"
explanation_text = "Spread misery and chaos upon the station."
completed = TRUE

/datum/outfit/teratoma
name = "Teratoma (Preview only)"

/datum/outfit/teratoma/post_equip(mob/living/carbon/human/human, visualsOnly)
human.set_species(/datum/species/teratoma)

#undef LAW_CORRUPTION_COOLDOWN_MAX
#undef LAW_CORRUPTION_COOLDOWN_MIN
#undef MAX_TERATOMA_ION_LAWS
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/// The maximum amount of teratomas for a single roll to shove into a pod.
#define MAX_TERATOMAS_TO_SPAWN 5

/datum/round_event_control/teratoma
name = "Teratoma Crash"
description = "Crashes a pod of up to 5 teratomas into the station."
typepath = /datum/round_event/ghost_role/teratoma
min_players = 35 // these are destructive
track = EVENT_TRACK_MAJOR
weight = 5
tags = list(TAG_COMBAT, TAG_DESTRUCTIVE, TAG_OUTSIDER_ANTAG, TAG_EXTERNAL, TAG_ALIEN)
checks_antag_cap = TRUE

/datum/round_event/ghost_role/teratoma
minimum_required = 1
role_name = "teratoma crash"
fakeable = FALSE //Nothing to fake here

/datum/round_event/ghost_role/teratoma/spawn_role()
var/list/candidates = SSpolling.poll_ghost_candidates(
question = "Do you want to be part of a group of teratomas crashing into the station?",
role = ROLE_TERATOMA,
ignore_category = POLL_IGNORE_TERATOMA,
alert_pic = /datum/antagonist/teratoma,
role_name_text = "teratoma crash",
chat_text_border_icon = /datum/antagonist/teratoma,
)

if(length(candidates) < 1)
return NOT_ENOUGH_PLAYERS

var/turf/maint_spawn = find_maintenance_spawn(atmos_sensitive = TRUE)
if(!maint_spawn) // this shouldn't happen
maint_spawn = get_safe_random_station_turf_equal_weight()
if(!maint_spawn) // this REALLY shouldn't happen
return MAP_ERROR

var/pod_type = pick(/obj/structure/closet/supplypod/car_pod, /obj/structure/closet/supplypod/washer_pod)
var/obj/structure/closet/supplypod/pod = new pod_type
pod.stay_after_drop = TRUE
while(length(candidates) > 0 && length(spawned_mobs) < MAX_TERATOMAS_TO_SPAWN)
var/mob/dead/selected = pick_n_take(candidates)
if(QDELETED(selected) || !selected.client)
continue
var/datum/mind/goober_mind = new(selected.key)
goober_mind.active = TRUE

var/mob/living/carbon/human/species/teratoma/goober = new(pod)
goober_mind.transfer_to(goober)
goober_mind.add_antag_datum(/datum/antagonist/teratoma)
if(prob(20))
goober.adjust_drunk_effect(rand(15, 25))
// bomb immunity for just long enough for the pod to land
ADD_TRAIT(goober, TRAIT_BOMBIMMUNE, type)
addtimer(TRAIT_CALLBACK_REMOVE(goober, TRAIT_BOMBIMMUNE, type), 5 SECONDS)
spawned_mobs += goober
new /obj/effect/pod_landingzone(maint_spawn, pod)

return SUCCESSFUL_SPAWN

#undef MAX_TERATOMAS_TO_SPAWN
1 change: 1 addition & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -8396,6 +8396,7 @@
#include "monkestation\code\modules\storytellers\converted_events\solo\wizard.dm"
#include "monkestation\code\modules\storytellers\converted_events\solo\ghosts\nuclear_operative_ghost.dm"
#include "monkestation\code\modules\storytellers\converted_events\solo\ghosts\paradox_clone.dm"
#include "monkestation\code\modules\storytellers\converted_events\solo\ghosts\teratoma.dm"
#include "monkestation\code\modules\storytellers\converted_events\solo\ghosts\wizard.dm"
#include "monkestation\code\modules\storytellers\event_groups\_event_group.dm"
#include "monkestation\code\modules\storytellers\event_groups\groups.dm"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Antagonist, Category } from '../base';
import { multiline } from 'common/string';

const Teratoma: Antagonist = {
key: 'teratoma',
name: 'Teratoma',
description: [
multiline`
By all means, you should not exist. Your very existence is a sin against nature itself.
You are loyal to nobody, except the forces of chaos itself.
`,
'Spread misery and chaos upon the station.',
],
category: Category.Midround,
};

export default Teratoma;
Loading