Add Proteans to the game. (#3449)

## About The Pull Request

Adds the funny nanite blob dudes from Vigro code into the game. It's
more of a port of the idea since they're coded from the ground up. They
will be more of a utility focused species then a combat focused species.
**They are fragile.**

- Eats metal. You need metal to live and you need metal to heal. 
- Healed by materials. You feed Proteans metal to heal them.
- Difficult to kill, but extremely fragile. They are considered a
deathless species but they are stuck in their suit if they die. **You
have to order a new refactory from cargo to revive them**

**Order Refactory > Screwdriver suit > Insert refactory > Wait 5
minutes.**

- Easily dismembered. They have 30 seconds to recover their limbs and
pop them on or they will melt into nothingness. Can do a lengthy heal
which replaces missing limbs and easy to replace organs.
- Without a refactory, you will wither away.
- Without an orchastrator, you will have a lot of issues moving.
- You can lock your suit on someone. (OOC escape will work)
- You can assimilate modsuits.

- [x] OOC escape
- [x] Suit Transformation fixes
- [x] Modsuit Assimilation
- [x] Ensure organs are working
- [x] Species info and lore
- [x] Antag Proteans
- [x] Custom damage. Disable various surgeries.
- [x] Testing, polish, feedback.
- [x] Runtime and CI fixing.
- [ ] Live testing and balance.

## Why It's Good For The Game

This was the second to top vote on species people wanted to see added
and this is more custom mechanics then what is just a human reskin.

## Proof Of Testing


![NVIDIA_Overlay_bC6M2FThJP](https://github.com/user-attachments/assets/cb8c8594-f82c-408b-a9a1-e86a61bf8a0f)


![NVIDIA_Overlay_dbhHrYltOH](https://github.com/user-attachments/assets/cb643365-5512-465f-8582-337e24211b9f)

## Changelog

🆑 StrangeWeirdKitten, Majkl-J
add: New species: Proteans
/🆑

---------

Co-authored-by: Waterpig <49160555+Majkl-J@users.noreply.github.com>
Co-authored-by: Bubberbot <151680451+Bubberbot@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: aKromatopzia <94389683+aKromatopzia@users.noreply.github.com>
Co-authored-by: Jinshee <96621959+Jinshee@users.noreply.github.com>
Co-authored-by: Jinshee <manastra2536@gmail.com>
Co-authored-by: JustMeTheIInd <145101584+JustMeTheIInd@users.noreply.github.com>
Co-authored-by: nevimer <77420409+nevimer@users.noreply.github.com>
Co-authored-by: Odairu <39929315+Odairu@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
This commit is contained in:
The Sharkening
2025-05-13 13:05:46 -06:00
committed by GitHub
parent 497932c58a
commit 77616e8b0e
54 changed files with 1192 additions and 51 deletions

View File

@@ -131,6 +131,9 @@
//This limb is shadowy and will regen if shadowheal is active
#define BODYTYPE_SHADOW (1<<7)
// SKYRAT EDIT ADDITION
/// Nanomachine bodypart
#define BODYTYPE_NANO (1<<8)
///The limb fits a modular custom shape
#define BODYSHAPE_CUSTOM (1<<9)
///The limb fits a taur body
@@ -139,6 +142,7 @@
#define BODYSHAPE_HIDE_SHOES (1<<11)
///The limb causes glasses and hats to be drawn on layers 5 and 4 respectively. Currently used for snouts with the (Top) suffix, which are drawn on layer 6 and would normally cover facewear
#define BODYSHAPE_ALT_FACEWEAR_LAYER (1<<12)
// SKYRAT EDIT END

View File

@@ -34,6 +34,8 @@
#define ORGAN_EXTERNAL (1<<13)
/// This is a mutant organ, having this makes you a -derived mutant to health analyzers.
#define ORGAN_MUTANT (1<<14)
/// BUBBER EDIT ADD - Nanoswarm organ
#define ORGAN_NANOMACHINE (1<<15)
/// Scarring on the right eye
#define RIGHT_EYE_SCAR (1<<0)
@@ -44,6 +46,8 @@
#define IS_ORGANIC_LIMB(limb) (limb.bodytype & BODYTYPE_ORGANIC)
/// Helper to figure out if a limb is robotic
#define IS_ROBOTIC_LIMB(limb) (limb.bodytype & BODYTYPE_ROBOTIC)
/// Helper to figure out if a limb is a nanomachine limb. --- BUBBER EDIT
#define IS_NANO_LIMB(limb) (limb.bodytype & BODYTYPE_NANO) // BUBBER EDIT END
/// Helper to figure out if a limb is a peg limb
#define IS_PEG_LIMB(limb) (limb.bodytype & BODYTYPE_PEG)

View File

@@ -0,0 +1 @@
#define SPECIES_PROTEAN "protean"

View File

@@ -12,3 +12,7 @@
#define COMSIG_MOB_ALWAYS_APPLY_DAMAGE "mob_always_apply_damage"
/// From modular_zubbers/code/modules/disease/disease_transmission.dm
#define COMSIG_DISEASE_COUNT_UPDATE "disease_count_update"
/// From [/mob/living/carbon/human/verb/safeword]: (mob/living/carbon)
#define COMSIG_OOC_ESCAPE "ooc_escape"
/// From [/datum/outfit]: (datum/outfit)
#define COMSIG_OUTFIT_EQUIP "outfit_equip"

View File

@@ -18,7 +18,8 @@
#define issnail(A) (is_species(A, /datum/species/snail))
#define isluminescent(A) (is_species(A, /datum/species/jelly/luminescent))
#define isprimitivedemihuman(A) (is_species(A, /datum/species/human/felinid/primitive))
#define isshadekin(A) (is_species(A, /datum/species/shadekin)) //BUBBERSTATION EDIT
#define isshadekin(A) (is_species(A, /datum/species/shadekin))
#define isprotean(A) (is_species(A, /datum/species/protean))
//Antags
#define ishorrorling(A) (istype(A, /mob/living/simple_animal/hostile/true_changeling))
#define iscorticalborer(A) (istype(A, /mob/living/basic/cortical_borer))

View File

@@ -303,7 +303,7 @@
if(activate_msg)
CRASH("Failed to activate [user]'s [skillchip_instance], on job [src]. Failure message: [activate_msg]")
SEND_SIGNAL(user.dna.species, COMSIG_OUTFIT_EQUIP, src, visuals_only) // BUBBER EDIT: Proteans. See /datum/species/protean/proc/outfit_handling
user.update_body()
return TRUE

View File

@@ -23,29 +23,30 @@
if(isnull(chosen_one))
return NOT_ENOUGH_PLAYERS
//spawn the ninja and assign the candidate
/// BUBBER EDIT START
var/mob/living/carbon/human/ninja = create_space_ninja(spawn_location)
ninja.PossessByPlayer(chosen_one.key)
ninja.mind.add_antag_datum(/datum/antagonist/ninja)
spawned_mobs += ninja
// SKYRAT EDIT ADDITION BEGIN: Preference Ninjas
var/loadme = tgui_input_list(ninja, "Do you wish to load your character slot?", "Load Character?", list("Yes!", "No, I want to be random!"), default = "No, I want to be random!", timeout = 60 SECONDS)
var/codename
if(loadme == "Yes!")
ninja.client?.prefs?.safe_transfer_prefs_to(ninja)
codename = tgui_input_text(ninja.client, "What should your codename be?", "Agent Name", "[pick("Master", "Legendary", "Agent", "Shinobi", "Ninja")] [ninja.dna.species.name]", 42, FALSE, TRUE, 300 SECONDS)
codename ? codename : (codename = "[pick("Master", "Legendary", "Agent", "Shinobi", "Ninja")] [ninja.dna.species.name]")
ninja.name = codename
ninja.real_name = codename
ninja.dna.update_dna_identity()
else
ninja.randomize_human_appearance(~(RANDOMIZE_NAME|RANDOMIZE_SPECIES))
ninja.dna.update_dna_identity()
//BUBBER EDIT BEGIN
var/obj/item/mod/control/ninjamod = locate(/obj/item/mod/control/pre_equipped/ninja) in ninja.contents
var/obj/item/mod/module/dna_lock/reinforced/ninja_dna_lock = locate(/obj/item/mod/module/dna_lock/reinforced) in ninjamod.contents
ninja_dna_lock.on_use()// BUBBER EDIT END
if(!isprotean(ninja))
var/loadme = tgui_input_list(ninja, "Do you wish to load your character slot?", "Load Character?", list("Yes!", "No, I want to be random!"), default = "No, I want to be random!", timeout = 60 SECONDS)
var/codename
if(loadme == "Yes!")
ninja.client?.prefs?.safe_transfer_prefs_to(ninja)
codename = tgui_input_text(ninja.client, "What should your codename be?", "Agent Name", "[pick("Master", "Legendary", "Agent", "Shinobi", "Ninja")] [ninja.dna.species.name]", 42, FALSE, TRUE, 300 SECONDS)
codename ? codename : (codename = "[pick("Master", "Legendary", "Agent", "Shinobi", "Ninja")] [ninja.dna.species.name]")
ninja.name = codename
ninja.real_name = codename
ninja.dna.update_dna_identity()
else
ninja.randomize_human_appearance(~(RANDOMIZE_NAME|RANDOMIZE_SPECIES))
ninja.dna.update_dna_identity()
var/obj/item/mod/control/ninjamod = locate(isprotean(ninja) ? /obj/item/mod/control/pre_equipped/protean : /obj/item/mod/control/pre_equipped/ninja) in ninja.contents
var/obj/item/mod/module/dna_lock/reinforced/ninja_dna_lock = locate(/obj/item/mod/module/dna_lock/reinforced) in ninjamod.contents
ninja_dna_lock.on_use()
/// BUBBER EDIT END
// SKYRAT EDIT ADDITION END: Preference Ninjas
message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a space ninja by an event.")
ninja.log_message("was spawned as a ninja by an event.", LOG_GAME)

View File

@@ -321,6 +321,11 @@
// Makes use of tool act to prevent shoving stuff into our internal storage
/obj/item/mod/control/tool_act(mob/living/user, obj/item/tool, list/modifiers)
if(istype(tool, /obj/item/pai_card))
// Bubber Edit Start - Proteans can't interface with AIs
if(istype(src, /obj/item/mod/control/pre_equipped/protean))
balloon_alert(user, "unable to interface")
return NONE
// Bubber Edit End
if(!open)
balloon_alert(user, "cover closed!")
return NONE // shoves the card in the storage anyways
@@ -570,30 +575,30 @@
var/check_range = TRUE
return electrocute_mob(user, get_charge_source(), src, 0.7, check_range)
/obj/item/mod/control/proc/install(obj/item/mod/module/new_module, mob/user)
/obj/item/mod/control/proc/install(obj/item/mod/module/new_module, mob/user, silent = FALSE) // Bubber Edit: Silent = FALSE
for(var/obj/item/mod/module/old_module as anything in modules)
if(is_type_in_list(new_module, old_module.incompatible_modules) || is_type_in_list(old_module, new_module.incompatible_modules))
if(user)
if(user && !silent) // Bubber Edit: Silent arg
balloon_alert(user, "incompatible with [old_module]!")
playsound(src, 'sound/machines/scanner/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
return
return //Bubber Edit: Return False
var/complexity_with_module = complexity
complexity_with_module += new_module.complexity
if(complexity_with_module > complexity_max)
if(user)
if(user && !silent) // Bubber Edit: Silent arg
balloon_alert(user, "above complexity max!")
playsound(src, 'sound/machines/scanner/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
return
return FALSE //Bubber Edit: Return False
if(!new_module.has_required_parts(mod_parts))
if(user)
if(user && !silent) // Bubber Edit: Silent arg
balloon_alert(user, "lacking required parts!")
playsound(src, 'sound/machines/scanner/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
return
return FALSE //Bubber Edit:
if(!new_module.can_install(src))
if(user)
if(user && !silent) // Bubber Edit: Silent arg
balloon_alert(user, "can't install!")
playsound(src, 'sound/machines/scanner/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
return
return FALSE //Bubber Edit: Return False
new_module.forceMove(src)
modules += new_module
complexity += new_module.complexity
@@ -604,10 +609,10 @@
if(active && new_module.has_required_parts(mod_parts, need_active = TRUE))
new_module.on_part_activation()
new_module.part_activated = TRUE
if(user)
if(user && !silent) // Bubber Edit: Silent Arg
balloon_alert(user, "[new_module] added")
playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE)
return TRUE // Bubber Edit: Return True
/obj/item/mod/control/proc/uninstall(obj/item/mod/module/old_module, deleting = FALSE)
modules -= old_module
complexity -= old_module.complexity

View File

@@ -614,6 +614,14 @@
UnregisterSignal(mod, COMSIG_ATOM_EMAG_ACT)
/obj/item/mod/module/dna_lock/on_use()
/// Bubber Edit Start - Proteans
if(istype(mod, /obj/item/mod/control/pre_equipped/protean))
var/obj/item/mod/control/pre_equipped/protean/p_suit = mod
var/obj/item/mod/core/protean/p_core = mod.core
var/datum/species/protean/species = p_core.linked_species
if(species.owner.loc == p_suit.loc)
return balloon_alert(p_suit.wearer, "button unresponsive")
/// Bubber Edit End
dna = mod.wearer.dna.unique_enzymes
balloon_alert(mod.wearer, "dna updated")
drain_power(use_energy_cost)
@@ -629,6 +637,11 @@
/obj/item/mod/module/dna_lock/proc/dna_check(mob/user)
if(!iscarbon(user))
return FALSE
/// Bubber Edit Start
var/obj/item/mod/control/pre_equipped/protean/suit = mod
if(istype(suit))
return TRUE
/// Bubber Edit End
var/mob/living/carbon/carbon_user = user
if(!dna || (carbon_user.has_dna() && carbon_user.dna.unique_enzymes == dna))
return TRUE

View File

@@ -254,6 +254,8 @@
. = ..()
if(. != MOD_CANCEL_ACTIVATE || !isliving(user))
return
if(istype(mod, /obj/item/mod/control/pre_equipped/protean)) // BUBBER EDIT START
return // BUBBER EDIT END
if(mod.ai_assistant == user)
to_chat(mod.ai_assistant, span_danger("<B>fATaL EERRoR</B>: 381200-*#00CODE <B>BLUE</B>\nAI INTErFERenCE DEtECted\nACTi0N DISrEGArdED"))
return

View File

@@ -12,7 +12,7 @@
/datum/surgery/blood_filter/mechanic
name = "Hydraulics Purge"
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO // BUBBER EDIT
steps = list(
/datum/surgery_step/mechanic_open,
/datum/surgery_step/open_hatch,

View File

@@ -1530,7 +1530,7 @@
/// The actual effect of EMPs on the limb. Allows children to override it however they want
/obj/item/bodypart/proc/emp_effect(severity, protection)
if(!IS_ROBOTIC_LIMB(src))
if(!(IS_ROBOTIC_LIMB(src) || IS_NANO_LIMB(src))) // BUBBER EDIT - Proteans
return FALSE
// with defines at the time of writing, this is 2 brute and 1.5 burn
// 2 + 1.5 = 3,5, with 6 limbs thats 21, on a heavy 42

View File

@@ -12,7 +12,7 @@
/datum/surgery/brain_surgery/mechanic
name = "Wetware OS Diagnostics"
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO
possible_locs = list(BODY_ZONE_HEAD)
steps = list(
/datum/surgery_step/mechanic_open,

View File

@@ -16,7 +16,7 @@
/datum/surgery/gastrectomy/mechanic
name = "Nutrient Processing System Diagnostic"
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO
steps = list(
/datum/surgery_step/mechanic_open,
/datum/surgery_step/open_hatch,

View File

@@ -97,7 +97,7 @@
/datum/surgery/organ_manipulation/mechanic
name = "Hardware Manipulation"
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO
surgery_flags = SURGERY_SELF_OPERABLE | SURGERY_REQUIRE_LIMB | SURGERY_CHECK_TOOL_BEHAVIOUR
possible_locs = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD)
steps = list(

View File

@@ -23,7 +23,7 @@
/datum/surgery/advanced/brainwashing_sleeper/mechanic
name = "Sleeper Agent Reprogramming"
desc = "Malware which directly implants the sleeper protocol directive into the robotic patient's operating system, making it their absolute priority. It can be cleared using a mindshield implant."
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO
steps = list(
/datum/surgery_step/mechanic_open,
/datum/surgery_step/open_hatch,

View File

@@ -15,10 +15,10 @@
/// far away users will be able to see, and anyone farther won't see anything.
/// Dead users will receive updates no matter what, though you likely want to add
/// a [`ui_status_only_living`] check for finer observer interactions.
/proc/ui_status_user_is_adjacent(mob/user, atom/source, allow_tk = TRUE)
/proc/ui_status_user_is_adjacent(mob/user, atom/source, allow_tk = TRUE, viewcheck = TRUE) // Bubber edit: Adds viewcheck
if (isliving(user))
var/mob/living/living_user = user
return living_user.shared_living_ui_distance(source, allow_tk = allow_tk)
return living_user.shared_living_ui_distance(source, viewcheck, allow_tk = allow_tk) // Bubber edit: Adds viewcheck
else
return UI_UPDATE

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

View File

@@ -413,6 +413,7 @@ ROUNDSTART_RACES shadekin
## BUBBER RACES DOWN HERE
ROUNDSTART_RACES lesser_abductor
ROUNDSTART_RACES zombie
ROUNDSTART_RACES protean
##-------------------------------------------------------------------------------------------

View File

@@ -5,6 +5,7 @@
mutantpart_info = list(MUTANT_INDEX_NAME = "Round", MUTANT_INDEX_COLOR_LIST = list("#FF4B19"))
slot = ORGAN_SLOT_EXTERNAL_CAP
preference = "feature_caps"
organ_flags = ORGAN_EXTERNAL
/obj/item/organ/mushroom_cap/Initialize(mapload)
if(!ispath(bodypart_overlay))

View File

@@ -4,6 +4,7 @@
icon = 'icons/obj/clothing/head/costume.dmi'
icon_state = "kitty"
bodypart_overlay = /datum/bodypart_overlay/mutant/ears
organ_flags = ORGAN_ORGANIC | ORGAN_EXTERNAL | ORGAN_VIRGIN
/obj/item/organ/ears/cat

View File

@@ -2,6 +2,7 @@
preference = "feature_frills"
mutantpart_key = "frills"
mutantpart_info = list(MUTANT_INDEX_NAME = "Divinity", MUTANT_INDEX_COLOR_LIST = list("#FFFFFF"))
organ_flags = ORGAN_EXTERNAL
/datum/bodypart_overlay/mutant/frills
color_source = ORGAN_COLOR_OVERRIDE

View File

@@ -3,6 +3,7 @@
preference = "feature_horns"
mutantpart_key = "horns"
mutantpart_info = list(MUTANT_INDEX_NAME = "Simple", MUTANT_INDEX_COLOR_LIST = list("#FFFFFF"))
organ_flags = ORGAN_EXTERNAL
/datum/bodypart_overlay/mutant/horns
layers = EXTERNAL_FRONT | EXTERNAL_ADJACENT | EXTERNAL_BEHIND

View File

@@ -1,3 +1,4 @@
/obj/item/organ/antennae
mutantpart_key = "moth_antennae"
mutantpart_info = list(MUTANT_INDEX_NAME = "Plain", MUTANT_INDEX_COLOR_LIST = list("#FFFFFF"))
organ_flags = ORGAN_UNREMOVABLE

View File

@@ -7,7 +7,7 @@
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_EXTERNAL_POD_HAIR
organ_flags = ORGAN_EXTERNAL
bodypart_overlay = /datum/bodypart_overlay/mutant/pod_hair
/datum/bodypart_overlay/mutant/pod_hair

View File

@@ -8,7 +8,7 @@
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_EXTERNAL_SKRELL_HAIR
organ_flags = ORGAN_EXTERNAL
preference = "feature_skrell_hair"
bodypart_overlay = /datum/bodypart_overlay/mutant/skrell_hair

View File

@@ -8,7 +8,7 @@
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_EXTERNAL_SYNTH_ANTENNA
organ_flags = ORGAN_UNREMOVABLE
preference = "feature_ipc_antenna"
bodypart_overlay = /datum/bodypart_overlay/mutant/synth_antenna

View File

@@ -8,6 +8,7 @@
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_EXTERNAL_SYNTH_SCREEN
organ_flags = ORGAN_EXTERNAL
preference = "feature_ipc_screen"

View File

@@ -3,6 +3,7 @@
mutantpart_info = list(MUTANT_INDEX_NAME = "Smooth", MUTANT_INDEX_COLOR_LIST = list("#FFFFFF"))
var/can_wag = TRUE
var/wagging = FALSE
organ_flags = ORGAN_EXTERNAL
/datum/bodypart_overlay/mutant/tail
color_source = ORGAN_COLOR_OVERRIDE

View File

@@ -5,6 +5,7 @@
slot = ORGAN_SLOT_WINGS
mutantpart_key = "wings"
mutantpart_info = list(MUTANT_INDEX_NAME = "Bat", MUTANT_INDEX_COLOR_LIST = list("#335533"))
organ_flags = ORGAN_EXTERNAL
///Whether the wings should grant flight on insertion.
var/unconditional_flight
///What species get flights thanks to those wings. Important for moth wings

View File

@@ -8,6 +8,7 @@
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_EXTERNAL_XENODORSAL
organ_flags = ORGAN_EXTERNAL
preference = "feature_xenodorsal"

View File

@@ -24,7 +24,6 @@
return TRUE
/mob/living/verb/shift_layer_down()
set name = "Shift Layer Downwards"
set category = "IC"

View File

@@ -52,7 +52,7 @@
set name = "OOC Safe Word"
set category = "OOC"
set desc = "Removes any and all lewd items from you."
SEND_SIGNAL(src, COMSIG_OOC_ESCAPE)
log_message("[key_name(src)] used the OOC Safe Word verb.", LOG_ATTACK)
for(var/obj/item/equipped_item in get_equipped_items())
if(!(equipped_item.type in GLOB.pref_checked_clothes))

View File

@@ -11,7 +11,7 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_CHEST) // The brains are in the chest
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO
desc = "A surgical procedure that restores the default behavior logic and personality matrix of an IPC posibrain."
/datum/surgery/robot_brain_surgery/can_start(mob/user, mob/living/carbon/target, obj/item/tool)

View File

@@ -1,7 +1,7 @@
/// Hydraulic Pump Surgery
/datum/surgery/hydraulic_maintenance
name = "Hydraulic Pump Maintenance"
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO
surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB
steps = list(
/datum/surgery_step/mechanic_open,

View File

@@ -4,7 +4,7 @@
surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB
organ_to_manipulate = ORGAN_SLOT_STOMACH
possible_locs = list(BODY_ZONE_CHEST)
requires_bodypart_type = BODYTYPE_ROBOTIC
requires_bodypart_type = BODYTYPE_ROBOTIC | BODYTYPE_NANO
steps = list(
/datum/surgery_step/mechanic_open,
/datum/surgery_step/mechanic_unwrench,

View File

@@ -51,3 +51,7 @@
/datum/surgery/stomach_pump/mechanic/can_start(mob/user, mob/living/carbon/target)
return !issynthetic(target) && ..()
/// Proteans can not heal via these surgeries.
/datum/surgery/healing/can_start(mob/user, mob/living/patient)
return !isprotean(patient) && ..()

View File

@@ -0,0 +1,13 @@
// Element added to some Protean organs.
/datum/element/nanite_organ
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
/datum/element/nanite_organ/Attach(obj/item/organ/target)
. = ..()
if(!istype(target) || (target.organ_flags & ORGAN_EXTERNAL))
return ELEMENT_INCOMPATIBLE
if(!istype(target, /obj/item/organ/eyes))
target.add_atom_colour(COLOR_GRAY, FIXED_COLOUR_PRIORITY) // Does a tree scream in the woods? More importantly, can you see the color of an organ that deletes itself?

View File

@@ -0,0 +1,12 @@
## Title: Proteans!
This folder primarily holds all the species logic for your favorite Nanomachine friend.
## TGUI Files:
## Core Proc Edits:
- /obj/item/mod/control/tool_act()
## Credits
- Waterpig / @Majkl-j
- The Sharkenning / @StrangeWeirdKitten

View File

@@ -0,0 +1,158 @@
/**THIS FILE MAY LOOK SCARY FUTURE CONTRIBUTOR. BUT DO NOT FRET.
* These are Macros. Basically we have multiple body parts with identical logic. Each Macro will be explained bellow.
* Instead of a 500 line file with identical code, element, or compoment: a macro is used as a short cut to condense the logic.
*/
/// Brute damage messages
#define LIGHT_NANO_BRUTE "scratched"
#define MEDIUM_NANO_BRUTE "festering"
#define HEAVY_NANO_BRUTE "falling apart"
/// Burn damage messages
#define LIGHT_NANO_BURN "scorched"
#define MEDIUM_NANO_BURN "melted"
#define HEAVY_NANO_BURN "boiling"
#define BRUTE_EXAMINE_NANO "deformation"
#define BURN_EXAMINE_NANO "scorching"
/obj/item/bodypart
var/bodypart_species
/// Sprite file location
#define PROTEAN_ORGAN_SPRITE 'modular_zubbers/icons/mob/species/protean/protean.dmi'
#define PROTEAN_STOMACH_FULL 10
#define PROTEAN_STOMACH_FALTERING 0.5
#define PROTEAN_METABOLISM_RATE 2000
#define PROTEAN_LIMB_TIME 30 SECONDS
/**
* PROTEAN_BODYPART_DEFINE(path, health) Macro
* This one is very simple. It is used to give a Protean's limbs the proper bodytypes and names.
* This is an alternative to creating each /obj/item/bodypart/ parent for every Protean limb.
*/
#define PROTEAN_BODYPART_DEFINE(path, health) \
##path {\
max_damage = ##health; \
bodypart_species = SPECIES_PROTEAN; \
bodytype = BODYTYPE_NANO; \
dmg_overlay_type = "robotic"; \
brute_modifier = 0.8; \
burn_modifier = 1.2; \
light_brute_msg = LIGHT_NANO_BRUTE; \
medium_brute_msg = MEDIUM_NANO_BRUTE; \
heavy_brute_msg = HEAVY_NANO_BRUTE; \
light_burn_msg = LIGHT_NANO_BURN; \
medium_burn_msg = MEDIUM_NANO_BURN; \
heavy_burn_msg = HEAVY_NANO_BURN; \
damage_examines = list(BRUTE = BRUTE_EXAMINE_NANO, BURN = BURN_EXAMINE_NANO); \
var/qdel_timer; \
}
/**
* PROTEAN_DELIMB_DEFINE(path) Macro
* Reworks the logic for delimbing. Once your limb gets mangled, it will fall off your body.
*/
#define PROTEAN_DELIMB_DEFINE(path) \
##path/try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus) {\
if(((get_damage() + wounding_dmg) >= max_damage)) {\
dismember(); \
qdel_timer = QDEL_IN_STOPPABLE(src, PROTEAN_LIMB_TIME); \
} \
}
/**
* PROTEAN_LIMB_ATTACH(path) Macro
* If you reattch your limb, it will delete the qdel timer.
*/
#define PROTEAN_LIMB_ATTACH(path) \
##path/can_attach_limb(limb_owner, special) {\
. = ..(); \
if(!.) {\
return FALSE; \
} \
if(!isnull(qdel_timer)) { \
deltimer(qdel_timer); \
return TRUE; \
} \
}
// Core
PROTEAN_BODYPART_DEFINE(/obj/item/bodypart/head/mutant/protean, 120)
PROTEAN_DELIMB_DEFINE(/obj/item/bodypart/head/mutant/protean)
PROTEAN_LIMB_ATTACH(/obj/item/bodypart/head/mutant/protean)
PROTEAN_BODYPART_DEFINE(/obj/item/bodypart/chest/mutant/protean, LIMB_MAX_HP_CORE)
// Limbs
PROTEAN_BODYPART_DEFINE(/obj/item/bodypart/arm/left/mutant/protean, 40)
PROTEAN_BODYPART_DEFINE(/obj/item/bodypart/arm/right/mutant/protean, 40)
/// Legs are a little more special, so they're not macro'd
/obj/item/bodypart/leg/right/mutant/protean
max_damage = 40
bodypart_species = SPECIES_PROTEAN
bodytype = BODYTYPE_NANO
dmg_overlay_type = "robotic"
brute_modifier = 0.8
burn_modifier = 1.2
light_brute_msg = LIGHT_NANO_BRUTE
medium_brute_msg = MEDIUM_NANO_BRUTE
heavy_brute_msg = HEAVY_NANO_BRUTE
light_burn_msg = LIGHT_NANO_BURN
medium_burn_msg = MEDIUM_NANO_BURN
heavy_burn_msg = HEAVY_NANO_BRUTE
damage_examines = list(BRUTE = BRUTE_EXAMINE_NANO, BURN = BURN_EXAMINE_NANO)
digitigrade_type = /obj/item/bodypart/leg/right/mutant/protean/digitigrade
var/qdel_timer
/obj/item/bodypart/leg/left/mutant/protean
max_damage = 40
bodypart_species = SPECIES_PROTEAN
bodytype = BODYTYPE_NANO
dmg_overlay_type = "robotic"
brute_modifier = 0.8
burn_modifier = 1.2
light_brute_msg = LIGHT_NANO_BRUTE
medium_brute_msg = MEDIUM_NANO_BRUTE
heavy_brute_msg = HEAVY_NANO_BRUTE
light_burn_msg = LIGHT_NANO_BURN
medium_burn_msg = MEDIUM_NANO_BURN
heavy_burn_msg = HEAVY_NANO_BRUTE
damage_examines = list(BRUTE = BRUTE_EXAMINE_NANO, BURN = BURN_EXAMINE_NANO)
digitigrade_type = /obj/item/bodypart/leg/left/mutant/protean/digitigrade
var/qdel_timer
/obj/item/bodypart/leg/right/mutant/protean/digitigrade
icon_greyscale = BODYPART_ICON_MAMMAL
limb_id = BODYPART_ID_DIGITIGRADE
bodyshape = parent_type::bodyshape | BODYSHAPE_DIGITIGRADE
base_limb_id = BODYPART_ID_DIGITIGRADE
/obj/item/bodypart/leg/right/mutant/protean/digitigrade/update_limb(dropping_limb = FALSE, is_creating = FALSE)
. = ..()
check_mutant_compatability()
/obj/item/bodypart/leg/left/mutant/protean/digitigrade
icon_greyscale = BODYPART_ICON_MAMMAL
limb_id = BODYPART_ID_DIGITIGRADE
bodyshape = parent_type::bodyshape | BODYSHAPE_DIGITIGRADE
base_limb_id = BODYPART_ID_DIGITIGRADE
/obj/item/bodypart/leg/left/mutant/protean/digitigrade/update_limb(dropping_limb = FALSE, is_creating = FALSE)
. = ..()
check_mutant_compatability()
PROTEAN_DELIMB_DEFINE(/obj/item/bodypart/arm/left/mutant/protean)
PROTEAN_DELIMB_DEFINE(/obj/item/bodypart/arm/right/mutant/protean)
PROTEAN_DELIMB_DEFINE(/obj/item/bodypart/leg/left/mutant/protean)
PROTEAN_DELIMB_DEFINE(/obj/item/bodypart/leg/right/mutant/protean)
PROTEAN_LIMB_ATTACH(/obj/item/bodypart/arm/left/mutant/protean)
PROTEAN_LIMB_ATTACH(/obj/item/bodypart/arm/right/mutant/protean)
PROTEAN_LIMB_ATTACH(/obj/item/bodypart/leg/left/mutant/protean)
PROTEAN_LIMB_ATTACH(/obj/item/bodypart/leg/right/mutant/protean)
#undef PROTEAN_BODYPART_DEFINE
#undef PROTEAN_DELIMB_DEFINE
#undef PROTEAN_LIMB_ATTACH

View File

@@ -0,0 +1,173 @@
/datum/species/protean
id = SPECIES_PROTEAN
examine_limb_id = SPECIES_PROTEAN
name = "Protean"
sexes = TRUE
siemens_coeff = 1.5 // Electricty messes you up.
payday_modifier = 0.7 // 30 percent poorer
exotic_blood = /datum/reagent/medicine/nanite_slurry
digitigrade_customization = DIGITIGRADE_OPTIONAL
meat = /obj/item/stack/sheet/iron
mutant_bodyparts = list()
mutantbrain = /obj/item/organ/brain/protean
mutantheart = /obj/item/organ/heart/protean
mutantstomach = /obj/item/organ/stomach/protean
mutantlungs = null
mutantliver = null
mutantappendix = null
mutanteyes = /obj/item/organ/eyes/robotic/protean
mutantears = /obj/item/organ/ears/cybernetic/protean
mutanttongue = /obj/item/organ/tongue/cybernetic/protean
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT
bodypart_overrides = list(
BODY_ZONE_HEAD = /obj/item/bodypart/head/mutant/protean,
BODY_ZONE_CHEST = /obj/item/bodypart/chest/mutant/protean,
BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/mutant/protean,
BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/mutant/protean,
BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/mutant/protean,
BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/mutant/protean,
)
inherent_traits = list(
// Default Species
TRAIT_ADVANCEDTOOLUSER,
TRAIT_CAN_STRIP,
TRAIT_LITERATE,
TRAIT_MUTANT_COLORS,
// Needed to exist without dying and robot specific stuff.
TRAIT_NOBREATH,
TRAIT_LIVERLESS_METABOLISM,
TRAIT_ROCK_EATER,
TRAIT_STABLEHEART, // TODO: handle orchestrator code
TRAIT_NOHUNGER, // They will have metal stored in the stomach. Fuck nutrition code.
TRAIT_LIMBATTACHMENT,
// Synthetic lifeforms
TRAIT_GENELESS,
TRAIT_NO_HUSK,
TRAIT_NO_DNA_SCRAMBLE,
TRAIT_SYNTHETIC, // Not used in any code, but just in case
TRAIT_TOXIMMUNE,
TRAIT_NEVER_WOUNDED, // Does not wound.
// Extra cool stuff
TRAIT_RADIMMUNE,
TRAIT_EASYDISMEMBER,
TRAIT_RDS_SUPPRESSED,
TRAIT_MADNESS_IMMUNE,
// Seperate handling will be used. Proteans never truely "die". They get stuck in their suit.
TRAIT_NODEATH,
//TRAIT_VENTCRAWLER_NUDE, - A tease. If you want to give a species vent crawl. God help your soul. But I won't stop you from learning that hard lesson.
)
inherent_biotypes = MOB_ROBOTIC | MOB_HUMANOID
reagent_flags = null
/// Reference to the
var/obj/item/mod/control/pre_equipped/protean/species_modsuit
/// Reference to the species owner
var/mob/living/carbon/human/owner
/mob/living/carbon/human/species/protean
race = /datum/species/protean
/datum/species/protean/Destroy(force)
QDEL_NULL(species_modsuit)
owner = null
. = ..()
/datum/species/protean/on_species_gain(mob/living/carbon/human/gainer, datum/species/old_species, pref_load, regenerate_icons = TRUE)
. = ..()
owner = gainer
equip_modsuit(gainer)
RegisterSignal(src, COMSIG_OUTFIT_EQUIP, PROC_REF(outfit_handling))
var/obj/item/mod/core/protean/core = species_modsuit.core
core?.linked_species = src
var/static/protean_verbs = list(
/mob/living/carbon/proc/protean_ui,
/mob/living/carbon/proc/protean_heal,
/mob/living/carbon/proc/lock_suit,
/mob/living/carbon/proc/suit_transformation,
)
add_verb(gainer, protean_verbs)
/datum/species/protean/on_species_loss(mob/living/carbon/human/gainer, datum/species/new_species, pref_load)
. = ..()
if(species_modsuit.stored_modsuit)
species_modsuit.unassimilate_modsuit(owner, TRUE)
gainer.dropItemToGround(species_modsuit, TRUE)
if(species_modsuit)
QDEL_NULL(species_modsuit)
owner = null
/datum/species/protean/proc/equip_modsuit(mob/living/carbon/human/gainer)
species_modsuit = new()
var/obj/item/item_in_slot = gainer.get_item_by_slot(ITEM_SLOT_BACK)
if(item_in_slot)
if(HAS_TRAIT(item_in_slot, TRAIT_NODROP))
stack_trace("Protean modsuit forced dropped a TRAIT_NODROP item on species equip. Type: [item_in_slot]")
gainer.dropItemToGround(item_in_slot, force = TRUE)
return gainer.equip_to_slot_if_possible(species_modsuit, ITEM_SLOT_BACK, disable_warning = TRUE)
/**
* Protean Outfit Handling and Logic ----------------------------------------
* Proteans get really fucky with outfit logic, so I've appended a COMSIG_OUTFIT_EQUIP signal at the end of /datum/outfit/proc/equip.
* Basically what this does, is once outfit code has been ran, it will go through the assigned outfit again.
* It assimilates any modsuits, gives you a storage if you're missing it, and places contents into said storage.
* Yes, this is really snowflakey but I've been bashing my head against the wall for 4 hours trying to figure this out.
* -------------------------------------------------------------------------- */
/datum/species/protean/proc/outfit_handling(datum/species/protean, datum/outfit/outfit, visuals_only) // Very snowflakey code. I'm not making outfits for every job.
SIGNAL_HANDLER
var/get_a_job = istype(outfit, /datum/outfit/job)
var/obj/item/mod/control/suit
if(ispath(outfit.back, /obj/item/mod/control))
var/control_path = outfit.back
suit = new control_path()
INVOKE_ASYNC(species_modsuit, TYPE_PROC_REF(/obj/item/mod/control/pre_equipped/protean, assimilate_modsuit), owner, suit, TRUE)
INVOKE_ASYNC(species_modsuit, TYPE_PROC_REF(/obj/item/mod/control, quick_activation))
var/obj/item/mod/module/storage/storage = locate() in species_modsuit.modules // Give a storage if we don't have one.
if(!storage)
storage = new()
species_modsuit.install(storage, owner, TRUE)
if(outfit.backpack_contents)
outfit.backpack_contents += /obj/item/stack/sheet/iron/twenty
for(var/path in outfit.backpack_contents)
if(!get_a_job)
continue
var/number = outfit.backpack_contents[path]
if(!isnum(number))//Default to 1
number = 1
for(var/i in 1 to number) // Copy and paste of EQUIP_OUTFIT_ITEM
owner.equip_to_slot_or_del(SSwardrobe.provide_type(path, owner), ITEM_SLOT_BACKPACK, TRUE, TRUE)
var/obj/item/outfit_item = owner.get_item_by_slot(ITEM_SLOT_BACKPACK)
if(outfit_item && outfit_item.type == path && !get_a_job)
outfit_item.on_outfit_equip(owner, visuals_only, ITEM_SLOT_BACKPACK)
/datum/species/protean/get_default_mutant_bodyparts()
return list(
"legs" = list("Normal Legs", FALSE)
)
/datum/species/protean/allows_food_preferences()
return FALSE
/datum/species/protean/get_species_description()
return list(
"Trillions of small machines swarm into a single crewmember. This is a Protean, a walking coherent blob of metallic mass, and a churning factory that turns materials into more of itself. \
Proteans are unkillable. Instead, they shunt themselves away into their core when catastrophic losses to their swarm occur. Their cores also mimic the functions of a modsuit and can even assimilate more functional suits to use. \
Proteans only have a few vital organs, which can only be replaced via cargo. Their refactory is a miniature factory, and without it, they will face slow, agonizing degradation. Their Orchestrator is a miniature processor required for ease of movement. \
Proteans are an extremely fragile species, weak in combat, but a powerful aid, or a puppeteer pulling the strings.")

View File

@@ -0,0 +1,181 @@
/**
* HANDLES ALL OF PROTEAN EXISTENCE CODE.
* Very snowflakey species. This is the communication chain.
* Brain > Refactory > Modsuit Core > Modsuit
* Brain > Orchestrator
* Brain > Murder every other organ you try to shove in this thing.
*/
/obj/item/organ/brain/protean
name = "protean core"
desc = "An advanced positronic brain, typically found in the core of a protean"
icon = PROTEAN_ORGAN_SPRITE
icon_state = "posi1"
zone = BODY_ZONE_CHEST
organ_flags = ORGAN_ROBOTIC | ORGAN_NANOMACHINE
organ_traits = list(TRAIT_SILICON_EMOTES_ALLOWED)
/// Whether or not the protean is stuck in their suit or not.
var/dead = FALSE
COOLDOWN_DECLARE(message_cooldown)
COOLDOWN_DECLARE(refactory_cooldown)
COOLDOWN_DECLARE(orchestrator_cooldown)
/obj/item/organ/brain/protean/on_life(seconds_per_tick, times_fired)
. = ..()
if(dead)
return
var/damage_amount = 4 // How much damage per life() tick this organ should apply
for(var/obj/item/organ/organ in owner.organs)
if(organ.organ_flags & (ORGAN_ROBOTIC | ORGAN_NANOMACHINE | ORGAN_EXTERNAL | ORGAN_UNREMOVABLE))
continue
if(istype(organ, /obj/item/organ/taur_body))
continue
organ.apply_organ_damage(damage_amount)
if(COOLDOWN_FINISHED(src, message_cooldown))
to_chat(owner, span_warning("Your mass violently rips apart [organ]!"))
COOLDOWN_START(src, message_cooldown, 30 SECONDS)
if(organ.organ_flags & ORGAN_FAILING)
to_chat(owner, span_warning("Your mass violently rejects [organ]"))
organ.mob_remove(owner, TRUE)
handle_refactory(owner.get_organ_slot(ORGAN_SLOT_STOMACH))
handle_orchestrator(owner.get_organ_slot(ORGAN_SLOT_HEART))
if(owner.stat >= HARD_CRIT && !dead)
to_chat(owner, span_red("Your fragile refactory withers away with your mass reduced to scraps. Someone will have to help you."))
dead = TRUE
owner.fully_heal(HEAL_DAMAGE)
qdel(owner.get_organ_slot(ORGAN_SLOT_STOMACH))
go_into_suit(TRUE)
/obj/item/organ/brain/protean/proc/handle_refactory(obj/item/organ) // Slowly degrade
var/datum/species/protean/species = owner?.dna.species
var/obj/item/mod/control/pre_equipped/protean/suit = species.species_modsuit
if(owner.loc == suit)
return
if(isnull(organ) || !istype(organ, /obj/item/organ/stomach/protean))
owner.adjustBruteLoss(3, forced = TRUE)
if(COOLDOWN_FINISHED(src, refactory_cooldown))
to_chat(owner, span_warning("Your mass is slowly degrading without your refactory!"))
COOLDOWN_START(src, refactory_cooldown, 30 SECONDS)
/obj/item/organ/brain/protean/proc/handle_orchestrator(obj/item/organ) // If you're missing an orchestrator, you will have trouble walking.
var/datum/species/protean/species = owner?.dna.species
var/obj/item/mod/control/pre_equipped/protean/suit = species.species_modsuit
if(owner.loc == suit)
return
if(!COOLDOWN_FINISHED(src, orchestrator_cooldown))
return
if(isnull(organ) || !istype(organ, /obj/item/organ/heart/protean))
owner.KnockToFloor(TRUE, TRUE, 1 SECONDS)
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/protean_slowdown, multiplicative_slowdown = 2)
to_chat(owner, span_warning("You're struggling to walk without your orchestrator!"))
else
owner.remove_movespeed_modifier(/datum/movespeed_modifier/protean_slowdown)
COOLDOWN_START(src, orchestrator_cooldown, 30 SECONDS)
/datum/movespeed_modifier/protean_slowdown
variable = TRUE
/obj/item/organ/brain/protean/proc/go_into_suit(forced)
var/datum/species/protean/protean = owner.dna?.species
if(!istype(protean) || owner.loc == protean.species_modsuit)
return
if(!forced)
if(!do_after(owner, 5 SECONDS))
return
owner.extinguish_mob()
var/obj/item/mod/control/pre_equipped/protean/suit = protean.species_modsuit
owner.invisibility = 101
new /obj/effect/temp_visual/protean_to_suit(owner.loc, owner.dir)
owner.Stun(INFINITY, TRUE)
suit.drop_suit()
owner.forceMove(suit)
sleep(12) //Sleep is fine here because I'm not returning anything and if the brain gets deleted within 12 ticks of this being ran, we have some other serious issues.
owner.invisibility = initial(owner.invisibility)
/obj/item/organ/brain/protean/proc/leave_modsuit()
var/datum/species/protean/protean = owner.dna?.species
if(!istype(protean))
return
var/obj/item/mod/control/pre_equipped/protean/suit = protean.species_modsuit
if(dead)
to_chat(owner, span_warning("Your mass is destroyed. You are unable to leave."))
return
if(!do_after(owner, 5 SECONDS, suit, IGNORE_INCAPACITATED))
return
var/mob/living/carbon/mob = suit.loc
if(istype(mob))
mob.dropItemToGround(suit, TRUE)
var/datum/storage/storage = suit.loc.atom_storage
if(istype(storage))
storage.remove_single(null, suit, get_turf(suit), TRUE)
suit.invisibility = 101
new /obj/effect/temp_visual/protean_from_suit(suit.loc, owner.dir)
sleep(12) //Same as above
suit.drop_suit()
owner.forceMove(suit.loc)
if(owner.get_item_by_slot(ITEM_SLOT_BACK))
owner.dropItemToGround(owner.get_item_by_slot(ITEM_SLOT_BACK), TRUE, TRUE, TRUE)
owner.equip_to_slot_if_possible(suit, ITEM_SLOT_BACK, disable_warning = TRUE)
suit.invisibility = initial(suit.invisibility)
addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living, SetStun), 0), 5 SECONDS)
if(!HAS_TRAIT(suit, TRAIT_NODROP))
ADD_TRAIT(suit, TRAIT_NODROP, "protean")
/obj/item/organ/brain/protean/proc/replace_limbs()
var/obj/item/organ/stomach/protean/stomach = owner.get_organ_slot(ORGAN_SLOT_STOMACH)
var/obj/item/organ/eyes/robotic/protean/eyes = owner.get_organ_slot(ORGAN_SLOT_EYES)
var/obj/item/organ/tongue/cybernetic/protean/tongue = owner.get_organ_slot(ORGAN_SLOT_TONGUE)
var/obj/item/organ/ears/cybernetic/protean/ears = owner.get_organ_slot(ORGAN_SLOT_EARS)
if(stomach.metal <= PROTEAN_STOMACH_FULL * 0.6 && istype(stomach))
to_chat(owner, span_warning("Not enough metal to heal body!"))
return
if(!istype(owner.loc, /obj/item/mod/control))
to_chat(owner, span_warning("Not in the open. You must be inside your suit!"))
return
var/datum/species/protean/species = owner.dna.species
if(!do_after(owner, 30 SECONDS, species.species_modsuit, IGNORE_INCAPACITATED))
return
stomach.metal = clamp(stomach.metal - (PROTEAN_STOMACH_FULL * 0.6), 0, 10)
owner.fully_heal(HEAL_LIMBS)
if(isnull(eyes))
eyes = new /obj/item/organ/eyes/robotic/protean
eyes.on_bodypart_insert()
eyes.Insert(owner, TRUE)
if(isnull(tongue))
tongue = new /obj/item/organ/tongue/cybernetic/protean
tongue.on_bodypart_insert()
tongue.Insert(owner, TRUE)
if(isnull(ears))
ears = new /obj/item/organ/ears/cybernetic/protean
ears.on_bodypart_insert()
ears.Insert(owner, TRUE)
/obj/item/organ/brain/protean/proc/revive()
dead = FALSE
playsound(owner, 'sound/machines/ping.ogg', 30)
to_chat(owner, span_warning("You have regained all your mass!"))
owner.fully_heal()
/obj/item/organ/brain/protean/proc/revive_timer()
balloon_alert_to_viewers("repairing")
addtimer(CALLBACK(src, PROC_REF(revive)), 5 MINUTES) // Bump to 5 minutes
/obj/effect/temp_visual/protean_to_suit
name = "to_suit"
icon = PROTEAN_ORGAN_SPRITE
icon_state = "to_puddle"
duration = 12
/obj/effect/temp_visual/protean_from_suit
name = "from_suit"
icon = PROTEAN_ORGAN_SPRITE
icon_state = "from_puddle"
duration = 12

View File

@@ -0,0 +1,287 @@
/obj/item/mod/control/pre_equipped/protean
name = "hardsuit rig"
desc = "The hardsuit rig unit of a Protean, allowing them to retract into it, or to deploy a suit that protects against various environments."
theme = /datum/mod_theme // Standard theme. TODO: Can be changed with standard mod armors
applied_core = /obj/item/mod/core/protean
applied_cell = null // Goes off stomach
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF // funny nanite
/// Whether or not the wearer can undeploy parts.
var/modlocked = FALSE
var/obj/item/mod/control/stored_modsuit
var/datum/mod_theme/stored_theme
/datum/mod_theme/protean
name = "Protean"
/obj/item/mod/control/pre_equipped/protean/Initialize(mapload, datum/mod_theme/new_theme, new_skin, obj/item/mod/core/new_core)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, "protean")
AddElement(/datum/element/strippable/protean, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human/, should_strip))
/obj/item/mod/control/pre_equipped/protean/Destroy()
if(stored_modsuit)
drop_suit()
INVOKE_ASYNC(src, PROC_REF(unassimilate_modsuit), null, forced = TRUE)
return ..()
/obj/item/mod/control/pre_equipped/protean/wrench_act(mob/living/user, obj/item/wrench)
return FALSE // Can't remove the core.
/obj/item/mod/control/pre_equipped/protean/emag_act(mob/user, obj/item/card/emag/emag_card)
return FALSE // Nope
/obj/item/mod/control/pre_equipped/protean/canStrip(mob/who)
return TRUE
/obj/item/mod/control/pre_equipped/protean/doStrip(mob/stripper, mob/owner) // Custom stripping code.
if(!isprotean(wearer)) // Strip it normally
REMOVE_TRAIT(src, TRAIT_NODROP, "protean") // Your ass is coming off.
return ..()
var/obj/item/mod/module/storage/inventory = locate() in src.modules
if(!isnull(inventory))
src.atom_storage.remove_all()
to_chat(stripper, span_notice("You empty out all the items from the Protean's internal storage module!"))
stripper.balloon_alert(stripper, "emptied storage")
return TRUE
to_chat(stripper, span_warning("This suit seems to be a part of them. You can't remove it!"))
stripper.balloon_alert(stripper, "can't strip a protean's suit!")
return ..()
/obj/item/mod/control/pre_equipped/protean/proc/drop_suit()
if(wearer)
if(HAS_TRAIT(src, TRAIT_NODROP))
REMOVE_TRAIT(src, TRAIT_NODROP, "protean")
wearer.dropItemToGround(src, TRUE, TRUE, TRUE)
/// Proteans can lock themselves on people.
/obj/item/mod/control/pre_equipped/protean/proc/toggle_lock(forced = FALSE)
if(modlocked && !forced && !isprotean(wearer))
REMOVE_TRAIT(src, TRAIT_NODROP, "protean")
modlocked = !modlocked
/obj/item/mod/control/pre_equipped/protean/equipped(mob/user, slot, initial)
. = ..()
if(isprotean(wearer))
return
if(slot == ITEM_SLOT_BACK && wearer)
RegisterSignal(wearer, COMSIG_OOC_ESCAPE, PROC_REF(ooc_escape))
if(modlocked)
ADD_TRAIT(src, TRAIT_NODROP, "protean")
to_chat(wearer, span_warning("The suit does not seem to be able to come off..."))
else
UnregisterSignal(wearer, COMSIG_OOC_ESCAPE)
/obj/item/mod/control/pre_equipped/protean/choose_deploy(mob/user)
if(!isprotean(user) && modlocked && active)
balloon_alert(user, "it refuses to listen")
return FALSE
return ..()
/obj/item/mod/control/pre_equipped/protean/toggle_activate(mob/user, force_deactivate)
if(!force_deactivate && modlocked && !isprotean(user) && active)
balloon_alert(user, "it doesn't turn off")
return FALSE
return ..()
/obj/item/mod/control/pre_equipped/protean/quick_deploy(mob/user)
if(!isprotean(user) && modlocked && active)
balloon_alert(user, "it won't undeploy")
return FALSE
return ..()
/obj/item/mod/control/pre_equipped/protean/retract(mob/user, obj/item/part, instant)
if(!isprotean(user) && modlocked && active && !instant)
balloon_alert(user, "that button is unresponsive")
return FALSE
return ..()
/// Protean Revivial
/obj/item/mod/control/pre_equipped/protean/tool_act(mob/living/user, obj/item/tool, list/modifiers)
. = ..()
var/obj/item/mod/core/protean/protean_core = core
var/obj/item/organ/brain/protean/brain = protean_core?.linked_species.owner.get_organ_slot(ORGAN_SLOT_BRAIN)
var/obj/item/organ/stomach/protean/refactory = protean_core.linked_species.owner.get_organ_slot(ORGAN_SLOT_STOMACH)
if(brain?.dead && open && istype(tool, /obj/item/organ/stomach/protean) && do_after(user, 10 SECONDS) && !refactory)
var/obj/item/organ/stomach = tool
stomach.Insert(protean_core.linked_species.owner, TRUE, DELETE_IF_REPLACED)
balloon_alert(user, "inserted!")
playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE)
brain.revive_timer()
return ITEM_INTERACT_SUCCESS
if(istype(tool, /obj/item/mod/control))
if(active)
balloon_alert(user, "turn it off")
return ITEM_INTERACT_BLOCKING
var/static/list/obj/item/mod/control/banned_modsuits = list(
/obj/item/mod/control/pre_equipped/infiltrator,
/obj/item/mod/control/pre_equipped/protean,)
if(is_type_in_list(tool, banned_modsuits))
balloon_alert(user, "incompatable")
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("The suit begins to slowly absorb [tool]!"))
if(!do_after(user, 4 SECONDS))
return ITEM_INTERACT_BLOCKING
assimilate_modsuit(user, tool)
return ITEM_INTERACT_SUCCESS
/obj/item/mod/control/pre_equipped/protean/ui_status(mob/user, datum/ui_state/state)
var/obj/item/mod/core/protean/source = core
var/datum/species/protean/species = source.linked_species
if(isprotean(species.owner) && species.owner == user && user.loc == src)
return 2
. = ..()
/obj/item/mod/control/pre_equipped/protean/proc/assimilate_modsuit(mob/user, modsuit, forced)
var/obj/item/mod/control/to_assimilate = modsuit
if(stored_modsuit)
to_chat(user, span_warning("Can't absorb two modsuits!"))
if(forced)
stack_trace("assimilate_modsuit: Tried to assimilate modsuit while there's already a stored modsuit. stored_modsuit: [stored_modsuit], new_modsuit: [to_assimilate]")
return
if(!user.transferItemToLoc(to_assimilate, src, forced))
balloon_alert(user, "stuck!")
return
if(!forced)
for(var/obj/item/part as anything in get_parts())
if(part.loc == src)
continue
retract(null, part, instant = TRUE)
stored_modsuit = to_assimilate
stored_theme = theme // Store the old theme in cache
theme = to_assimilate.theme // Set new theme
complexity_max = to_assimilate.complexity_max // Inheret complexity
skin = to_assimilate.skin // Inheret skin
theme.set_up_parts(src, skin) // Put everything together
name = to_assimilate.name
desc = to_assimilate.desc
extended_desc = to_assimilate.extended_desc
for(var/obj/item/mod/module/module in to_assimilate.modules) // Insert every module
if(install(module, user, TRUE))
continue
uninstall(module) // Drop it if failed
update_static_data_for_all_viewers()
/obj/item/mod/control/pre_equipped/protean/proc/unassimilate_modsuit(mob/living/user, forced = FALSE)
if(active && !forced)
balloon_alert(user, "deactivate modsuit")
return
if(!(user.has_active_hand()) && !forced)
balloon_alert(user, "need active hand")
return
if(!forced)
to_chat(user, span_notice("You begin to pry the assimilated modsuit away."))
if(!do_after(user, 4 SECONDS))
return
for(var/obj/item/part as anything in get_parts())
if(part.loc == src)
continue
retract(null, part, instant = TRUE)
complexity_max = initial(complexity_max)
for(var/obj/item/mod/module in modules) // Transfer back every module
if(stored_modsuit.install(module, user, TRUE))
continue
uninstall(module)
theme = stored_theme
stored_theme = null
skin = initial(skin)
theme.set_up_parts(src, skin)
name = initial(name)
desc = initial(desc)
extended_desc = initial(extended_desc)
//if(forced)
// stored_modsuit.forceMove(get_turf(src))
// stored_modsuit = null
if(user.can_put_in_hand(stored_modsuit, user.active_hand_index))
user.put_in_hand(stored_modsuit, user.active_hand_index)
stored_modsuit = null
update_static_data_for_all_viewers()
/obj/item/mod/control/pre_equipped/protean/verb/remove_modsuit()
set name = "Remove Assimilated Modsuit"
unassimilate_modsuit(usr)
/obj/item/mod/control/pre_equipped/protean/examine(mob/user)
. = ..()
var/obj/item/mod/core/protean/protean_core = core
var/obj/item/organ/brain/protean/brain = protean_core?.linked_species.owner.get_organ_slot(ORGAN_SLOT_BRAIN)
var/obj/item/organ/stomach/protean/refactory = protean_core.linked_species.owner.get_organ_slot(ORGAN_SLOT_STOMACH)
if(!isnull(brain) || istype(brain))
. += span_notice("<b>Control Shift Click</b> to open Protean strip menu.")
if(brain.dead)
if(!open)
. += isnull(refactory) ? span_warning("This Protean requires critical repairs! <b>Screwdriver them open</b>") : span_notice("<b>Repairing systems...</b>")
else
. += isnull(refactory) ? span_warning("<b>Insert a new refactory</b>") : span_notice("<b>Refactory Installed! Repairing systems...</b>")
/obj/item/mod/control/pre_equipped/protean/proc/ooc_escape(mob/living/carbon/user)
SIGNAL_HANDLER
if(isprotean(wearer))
return
drop_suit()
if(modlocked)
toggle_lock(TRUE)
/**
* Protean stripping while they're in the suit.
* Yeah I guess stripping is an element. Carry on, citizen.
*/
/datum/element/strippable/protean
/datum/element/strippable/protean/Attach(datum/target, list/items, should_strip_proc_path)
. = ..()
RegisterSignal(target, COMSIG_CLICK_CTRL_SHIFT, PROC_REF(click_control_shit))
/datum/element/strippable/protean/Detach(datum/source)
. = ..()
UnregisterSignal(source, COMSIG_CLICK_CTRL_SHIFT)
/datum/element/strippable/protean/proc/click_control_shit(datum/source, mob/user)
SIGNAL_HANDLER
var/obj/item/mod/control/pre_equipped/protean/suit = source
if(!istype(suit))
return
var/obj/item/mod/core/protean/core = suit.core
var/datum/species/protean/species = core.linked_species
if(species.owner == user)
return
if(suit.wearer == source)
return
if (!isnull(should_strip_proc_path) && !call(species.owner, should_strip_proc_path)(user))
return
suit.balloon_alert_to_viewers("stripping")
ASYNC
var/datum/strip_menu/protean/strip_menu = LAZYACCESS(strip_menus, species.owner)
if (isnull(strip_menu))
strip_menu = new(species.owner, src)
LAZYSET(strip_menus, species.owner, strip_menu)
strip_menu.ui_interact(user)
/datum/strip_menu/protean
/datum/strip_menu/protean/ui_status(mob/user, datum/ui_state/state) // Needs to pass a viewcheck.
return min(
ui_status_only_living(user, owner),
ui_status_user_has_free_hands(user, owner),
ui_status_user_is_adjacent(user, owner, allow_tk = FALSE, viewcheck = FALSE),
HAS_TRAIT(user, TRAIT_CAN_STRIP) ? UI_INTERACTIVE : UI_UPDATE,
max(
ui_status_user_is_conscious_and_lying_down(user),
ui_status_user_is_abled(user, owner),
),
)

View File

@@ -0,0 +1,57 @@
/obj/item/mod/core/protean
name = "MOD protean core"
icon_state = "mod-core-ethereal"
desc = "If you see this, go scream at a coder and tell them how you managed to do this."
/// We handle as many interactions as possible through the species datum
/// The species handles cleanup on this
var/datum/species/protean/linked_species
/obj/item/mod/core/protean/charge_source()
if(isnull(linked_species))
return
if(isnull(linked_species.owner))
return
return linked_species.owner.get_organ_slot(ORGAN_SLOT_STOMACH)
/obj/item/mod/core/protean/charge_amount()
var/obj/item/organ/stomach/protean/stomach = charge_source()
var/obj/item/organ/brain/protean/brain = linked_species.owner.get_organ_slot(ORGAN_SLOT_BRAIN)
if(!istype(stomach))
return null
if(brain.dead)
return null
return stomach.metal
/obj/item/mod/core/protean/max_charge_amount()
return PROTEAN_STOMACH_FULL
/// We don't charge in a standard way
/obj/item/mod/core/protean/add_charge(amount)
return FALSE
/obj/item/mod/core/protean/subtract_charge(amount)
return FALSE
/obj/item/mod/core/protean/check_charge(amount)
var/obj/item/organ/stomach/protean/stomach = charge_source()
var/obj/item/organ/brain/protean/brain = linked_species.owner.get_organ_slot(ORGAN_SLOT_BRAIN)
if(stomach.metal <= PROTEAN_STOMACH_FALTERING)
return FALSE
if(!istype(brain) || brain.dead)
return FALSE
return TRUE
/obj/item/mod/core/protean/get_charge_icon_state()
return charge_source() ? "0" : "missing"
/obj/item/mod/core/protean/get_chargebar_color()
if(isnull(charge_amount()))
return "transparent"
switch(charge_amount())
if (-INFINITY to (PROTEAN_STOMACH_FULL * 0.3))
return "bad"
if((PROTEAN_STOMACH_FULL * 0.3) to (PROTEAN_STOMACH_FULL * 0.7))
return "average"
if((PROTEAN_STOMACH_FULL * 0.7) to INFINITY)
return "good"

View File

@@ -0,0 +1,50 @@
/obj/item/organ/heart/protean
name = "orchestrator module"
desc = "A small computer, designed for highly parallel workloads."
icon = PROTEAN_ORGAN_SPRITE
icon_state = "orchestrator"
organ_flags = ORGAN_ROBOTIC | ORGAN_NANOMACHINE
/obj/item/organ/eyes/robotic/protean
name = "imaging nanites"
desc = "Nanites designed to collect visual data from the surrounding world"
organ_flags = ORGAN_ROBOTIC
flash_protect = FLASH_PROTECTION_WELDER
/obj/item/organ/eyes/robotic/protean/Initialize(mapload)
if(QDELETED(src))
return FALSE
return ..()
/obj/item/organ/eyes/robotic/protean/Initialize(mapload)
. = ..()
AddElement(/datum/element/nanite_organ)
/obj/item/organ/ears/cybernetic/protean
name = "sensory nanites"
desc = "Nanites designed to collect audio feedback from the surrounding world"
organ_flags = ORGAN_ROBOTIC
/obj/item/organ/ears/cybernetic/protean/Insert(mob/living/carbon/receiver, special, movement_flags)
if(QDELETED(src))
return FALSE
return ..()
/obj/item/organ/ears/cybernetic/protean/Initialize(mapload)
. = ..()
AddElement(/datum/element/nanite_organ)
/obj/item/organ/tongue/cybernetic/protean
name = "protean audio fabricator"
desc = "Millions of nanites vibrate in harmony to create the sound you hear."
organ_flags = ORGAN_ROBOTIC
/obj/item/organ/tongue/cybernetic/protean/Insert(mob/living/carbon/receiver, special, movement_flags)
if(QDELETED(src))
return FALSE
return ..()
/obj/item/organ/tongue/cybernetic/protean/Initialize(mapload)
. = ..()
AddElement(/datum/element/nanite_organ)

View File

@@ -0,0 +1,77 @@
/obj/item/organ/stomach/protean
name = "refactory"
desc = "An extremely fragile factory used to rescyle materials and create more nanite mass"
icon = PROTEAN_ORGAN_SPRITE
icon_state = "refactory"
organ_flags = ORGAN_ROBOTIC | ORGAN_NANOMACHINE
organ_traits = list(TRAIT_NOHUNGER)
/// How much max metal can we hold at any given time (In sheets). This isn't using nutrition code because nutrition code gets weird without livers.
var/metal_max = PROTEAN_STOMACH_FULL
/// How much metal are we holding currently (In sheets)
var/metal = PROTEAN_STOMACH_FULL
COOLDOWN_DECLARE(starving_message)
/obj/item/organ/stomach/protean/Initialize(mapload)
. = ..() // Call the rest of the proc
metal = round(rand(PROTEAN_STOMACH_FULL/2, PROTEAN_STOMACH_FULL))
/obj/item/organ/stomach/protean/on_mob_insert(mob/living/carbon/receiver, special, movement_flags)
. = ..()
RegisterSignal(receiver, COMSIG_CARBON_ATTEMPT_EAT, PROC_REF(try_stomach_eat))
/obj/item/organ/stomach/protean/on_mob_remove(mob/living/carbon/stomach_owner, special, movement_flags)
. = ..()
UnregisterSignal(stomach_owner, COMSIG_CARBON_ATTEMPT_EAT)
/obj/item/organ/stomach/protean/on_life(seconds_per_tick, times_fired)
var/datum/species/protean/species = owner?.dna.species
var/obj/item/mod/control/pre_equipped/protean/suit = species.species_modsuit
if(owner.loc == suit)
return
/// Zero out any nutrition. We do not use hunger in this species.
for(var/datum/reagent/consumable/food in reagents.reagent_list)
food.nutriment_factor = 0
. = ..()
handle_hunger_slowdown(owner, seconds_per_tick)
/obj/item/organ/stomach/protean/handle_hunger_slowdown(mob/living/carbon/human/human, seconds_per_tick)
if(!istype(owner.dna.species, /datum/species/protean))
return
if(metal > PROTEAN_STOMACH_FALTERING)
owner.remove_movespeed_modifier(/datum/movespeed_modifier/protean_slowdown)
if(owner.health < owner.maxHealth && metal > PROTEAN_STOMACH_FULL * 0.3)
metal -= clamp(((PROTEAN_STOMACH_FULL / PROTEAN_METABOLISM_RATE) * seconds_per_tick * 20), 0, 10) // Healing needs metal. 0.2 sheets per tick
owner.adjustBruteLoss(-2, forced = TRUE)
owner.adjustFireLoss(-2, forced = TRUE)
else
metal -= clamp(((PROTEAN_STOMACH_FULL / PROTEAN_METABOLISM_RATE) * seconds_per_tick), 0, 10)
return
owner.adjustBruteLoss(2, forced = TRUE)
if(COOLDOWN_FINISHED(src, starving_message))
to_chat(owner, span_warning("You are starving! You must find metal now!"))
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/protean_slowdown, multiplicative_slowdown = 2)
COOLDOWN_START(src, starving_message, 20 SECONDS)
/// Check to see if our metal storage is full.
/obj/item/organ/stomach/protean/proc/try_stomach_eat(mob/eater, atom/eating)
SIGNAL_HANDLER
if(istype(eating, /obj/item/food/golem_food))
var/obj/item/food/golem_food/food = eating
if(metal > (PROTEAN_STOMACH_FULL - 0.3) && food.owner.loc == owner)
balloon_alert(owner, "storage full!")
return COMSIG_CARBON_BLOCK_EAT
/// If we ate a sheet of metal, add it to storage.
/obj/item/organ/stomach/protean/after_eat(atom/edible)
if(istype(edible, /obj/item/food/golem_food))
var/obj/item/food/golem_food/food = edible
metal = clamp(metal + 1, 0, PROTEAN_STOMACH_FULL)
if(food.owner.loc != owner) // Other people feeding them will heal them.
owner.adjustBruteLoss(-20, forced = TRUE)
var/health_check = owner.health >= owner.maxHealth ? "fully healed!" : "healed!"
owner.balloon_alert_to_viewers("[health_check]")
#undef PROTEAN_STOMACH_FULL
#undef PROTEAN_STOMACH_FALTERING

View File

@@ -0,0 +1,60 @@
/mob/living/carbon/proc/protean_ui()
set name = "Open Suit UI"
set desc = "Opens your suit UI"
set category = "Protean"
var/datum/species/protean/species = dna.species
if(!istype(species))
return
species.species_modsuit.ui_interact(src)
/mob/living/carbon/proc/protean_heal()
set name = "Heal Organs and Limbs"
set desc = "Heals your replacable organs and limbs with 6 metal."
set category = "Protean"
var/obj/item/organ/brain/protean/brain = get_organ_slot(ORGAN_SLOT_BRAIN)
if(!istype(brain))
return
var/datum/species/protean/species = dna.species
var/obj/item/mod/control/pre_equipped/protean/suit = species.species_modsuit
if(incapacitated && loc != suit)
balloon_alert(src, "incapacitated!")
return
brain.replace_limbs()
/mob/living/carbon/proc/lock_suit()
set name = "Lock Suit"
set desc = "Locks your suit on someone"
set category = "Protean"
var/datum/species/protean/species = dna.species
if(!istype(species))
return
var/obj/item/mod/control/pre_equipped/protean/suit = species.species_modsuit
species.species_modsuit.toggle_lock()
to_chat(src, span_notice("You [suit.modlocked ? "<b>lock</b>" : "<b>unlock</b>"] the suit [isprotean(suit.wearer) || loc == suit ? "" : "onto [suit.wearer]"]"))
playsound(src, 'sound/machines/click.ogg', 25)
/mob/living/carbon/proc/suit_transformation()
set name = "Toggle Suit Transformation"
set desc = "Either leave or enter your suit."
set category = "Protean"
var/obj/item/organ/brain/protean/brain = get_organ_slot(ORGAN_SLOT_BRAIN)
if(!istype(brain))
return
var/datum/species/protean/species = dna.species
if(loc == species.species_modsuit)
brain.leave_modsuit()
else if(isturf(loc))
if(!incapacitated)
brain.go_into_suit()
else
balloon_alert(src, "incapacitated!")

View File

@@ -39,3 +39,12 @@
contains = list(/obj/item/firing_pin/alert_level = 4)
crate_name = "alert level firing pin crate"
/datum/supply_pack/science/protean_organs
name = "Protean Organs"
desc = "Contains two sets of organs for Protean crewmembers."
cost = CARGO_CRATE_VALUE * 10 // Not cheap
contains = list(/obj/item/organ/stomach/protean = 2, /obj/item/organ/heart/protean = 2)
crate_name = "protean organs"
access = ACCESS_ROBOTICS
access_view = ACCESS_ROBOTICS
crate_type = /obj/structure/closet/crate/secure/science/robo

View File

@@ -34,7 +34,8 @@
/// Restricted roles from the antag roll
var/restricted_roles = list(JOB_AI, JOB_CYBORG)
/// Restricted species from the antag roll
var/restricted_species = list()
/// How many baseline antags do we spawn
var/base_antags = 1
/// How many maximum antags can we spawn
@@ -81,7 +82,7 @@
/datum/round_event_control/antagonist/proc/get_candidates()
var/round_started = SSticker.HasRoundStarted()
var/list/candidates = SSgamemode.get_candidates(antag_flag, pick_roundstart_players = !round_started, restricted_roles = restricted_roles)
var/list/candidates = SSgamemode.get_candidates(antag_flag, pick_roundstart_players = !round_started, restricted_roles = restricted_roles, restricted_species = restricted_species)
return candidates
/datum/round_event_control/antagonist/solo

View File

@@ -8,7 +8,7 @@
min_players = 30
maximum_antags_global = 3
restricted_species = list(SPECIES_PROTEAN)
tags = list(TAG_COMBAT, TAG_SPOOKY, TAG_CREW_ANTAG)
/datum/round_event_control/antagonist/solo/bloodsucker/midround

View File

@@ -7,6 +7,7 @@
weight = 8
min_players = 20
maximum_antags_global = 4
restricted_species = list(SPECIES_PROTEAN)
tags = list(TAG_COMBAT, TAG_CREW_ANTAG)

View File

@@ -251,6 +251,7 @@ SUBSYSTEM_DEF(gamemode)
inherit_required_time = TRUE,
no_antags = TRUE,
list/restricted_roles,
list/restricted_species,
)
@@ -279,10 +280,13 @@ SUBSYSTEM_DEF(gamemode)
continue
if(restricted_roles && (candidate.mind.assigned_role.title in restricted_roles))
continue
var/mob/living/carbon/carbon = candidate
if(istype(carbon)) // species restrictions
if(restricted_species && (carbon.dna.species.id in restricted_species))
continue
if(special_role_flag)
if(!(candidate.client.prefs) || !(special_role_flag in candidate.client.prefs.be_special))
continue
var/time_to_check
if(required_time)
time_to_check = required_time

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -518,6 +518,7 @@
#include "code\__DEFINES\~~bubber_defines\chat.dm"
#include "code\__DEFINES\~~bubber_defines\combat.dm"
#include "code\__DEFINES\~~bubber_defines\cooldowns.dm"
#include "code\__DEFINES\~~bubber_defines\dna.dm"
#include "code\__DEFINES\~~bubber_defines\economy.dm"
#include "code\__DEFINES\~~bubber_defines\experisci.dm"
#include "code\__DEFINES\~~bubber_defines\footsteps.dm"
@@ -8874,6 +8875,7 @@
#include "modular_zubbers\code\datums\computer_datums\protolathe_modifications.dm"
#include "modular_zubbers\code\datums\diseases\advance\presets.dm"
#include "modular_zubbers\code\datums\elements\examine_when_worn.dm"
#include "modular_zubbers\code\datums\elements\nanite_organ.dm"
#include "modular_zubbers\code\datums\greyscale\config_types\greyscale_configs\greyscale_clothes.dm"
#include "modular_zubbers\code\datums\greyscale\config_types\greyscale_configs\pipe_carp.dm"
#include "modular_zubbers\code\datums\mapgen\Cavegens\moonstation.dm"
@@ -9224,6 +9226,14 @@
#include "modular_zubbers\code\modules\credits\credits.dm"
#include "modular_zubbers\code\modules\customization\height_scaling\icons.dm"
#include "modular_zubbers\code\modules\customization\height_scaling\preferences.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\__DEFINES.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\_proteans_species.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\protean_brain.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\protean_modsuit.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\protean_modsuit_core.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\protean_organs.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\protean_stomach.dm"
#include "modular_zubbers\code\modules\customization\species\proteans\protean_verbs.dm"
#include "modular_zubbers\code\modules\customization\species\synths\death_sound.dm"
#include "modular_zubbers\code\modules\customization\species\synths\code\synth_prefab.dm"
#include "modular_zubbers\code\modules\customization\sprite_accessories\64_wings.dm"