mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-09 16:05:07 +00:00
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   ## 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:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
1
code/__DEFINES/~~bubber_defines/dna.dm
Normal file
1
code/__DEFINES/~~bubber_defines/dna.dm
Normal file
@@ -0,0 +1 @@
|
||||
#define SPECIES_PROTEAN "protean"
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 |
@@ -413,6 +413,7 @@ ROUNDSTART_RACES shadekin
|
||||
## BUBBER RACES DOWN HERE
|
||||
ROUNDSTART_RACES lesser_abductor
|
||||
ROUNDSTART_RACES zombie
|
||||
ROUNDSTART_RACES protean
|
||||
|
||||
##-------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
zone = BODY_ZONE_HEAD
|
||||
slot = ORGAN_SLOT_EXTERNAL_SYNTH_SCREEN
|
||||
organ_flags = ORGAN_EXTERNAL
|
||||
|
||||
preference = "feature_ipc_screen"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
zone = BODY_ZONE_HEAD
|
||||
slot = ORGAN_SLOT_EXTERNAL_XENODORSAL
|
||||
organ_flags = ORGAN_EXTERNAL
|
||||
|
||||
preference = "feature_xenodorsal"
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
return TRUE
|
||||
|
||||
|
||||
/mob/living/verb/shift_layer_down()
|
||||
set name = "Shift Layer Downwards"
|
||||
set category = "IC"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) && ..()
|
||||
|
||||
13
modular_zubbers/code/datums/elements/nanite_organ.dm
Normal file
13
modular_zubbers/code/datums/elements/nanite_organ.dm
Normal 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?
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.")
|
||||
@@ -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
|
||||
@@ -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),
|
||||
),
|
||||
)
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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!")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
modular_zubbers/icons/mob/species/protean/protean.dmi
Normal file
BIN
modular_zubbers/icons/mob/species/protean/protean.dmi
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user