Merge remote-tracking branch 'citadel/master' into respawn_system_2

This commit is contained in:
silicons
2020-12-13 22:54:16 -07:00
54 changed files with 899 additions and 479 deletions
+3
View File
@@ -60,6 +60,9 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they
if(!dry_run && (sold || delete_unsold))
if(ismob(thing))
thing.investigate_log("deleted through cargo export",INVESTIGATE_CARGO)
if(ismecha(thing))
var/obj/mecha/mech = thing
mech.wreckage = null // why a mech left a wreck when sold i will never know
qdel(thing)
return report
+3
View File
@@ -53,6 +53,9 @@
new festive_tree(get_turf(src))
return INITIALIZE_HINT_QDEL
/obj/effect/landmark/xmastree/hilbert
christmas_tree = /obj/structure/flora/tree/pine/xmas
/obj/effect/landmark/xmastree/rdrod
name = "festivus pole spawner"
festive_tree = /obj/structure/festivus
@@ -34,6 +34,10 @@
filling_color = "#FF69B4"
return TRUE
/// Returns the sprite of the donut while in a donut box
/obj/item/reagent_containers/food/snacks/donut/proc/in_box_sprite()
return "[icon_state]_inbox"
/obj/item/reagent_containers/food/snacks/donut/checkLiked(fraction, mob/M) //Sec officers always love donuts
if(last_check_time + 50 < world.time)
if(ishuman(M))
@@ -165,6 +169,10 @@
tastes = list("jelly" = 1, "donut" = 3)
foodtype = JUNKFOOD | GRAIN | FRIED | FRUIT | SUGAR | BREAKFAST
// Jelly donuts don't have holes, but look the same on the outside
/obj/item/reagent_containers/food/snacks/donut/jelly/in_box_sprite()
return "[replacetext(icon_state, "jelly", "donut")]_inbox"
/obj/item/reagent_containers/food/snacks/donut/jelly/Initialize()
. = ..()
if(extra_reagent)
+57
View File
@@ -0,0 +1,57 @@
/**
* Sets an ability property
*/
/mob/proc/set_ability_property(ability, property, value)
LAZYINITLIST(ability_properties)
LAZYINITLIST(ability_properties[ability])
ability_properties[ability][property] = value
/**
* Gets an ability property
*/
/mob/proc/get_ability_property(ability, property)
return ability_properties && ability_properties[ability] && ability_properties[ability][property]
GLOBAL_LIST_INIT(innate_ability_typepaths, all_innate_ability_typepaths())
/proc/all_innate_ability_typepaths()
return list(
INNATE_ABILITY_HUMANOID_CUSTOMIZATION = /datum/action/innate/ability/humanoid_customization,
INNATE_ABILITY_SLIME_BLOBFORM = /datum/action/innate/ability/slime_blobform,
INNATE_ABILITY_LIMB_REGROWTH = /datum/action/innate/ability/limb_regrowth
)
/**
* Grants an ability from a source
*/
/mob/proc/grant_ability_from_source(list/abilities, source)
if(!islist(abilities))
abilities = list(abilities)
LAZYINITLIST(ability_actions)
LAZYINITLIST(innate_abilities)
for(var/ability in abilities)
LAZYINITLIST(innate_abilities[ability])
innate_abilities[ability] |= source
if(ability_actions[ability])
continue
var/path = GLOB.innate_ability_typepaths[ability]
var/datum/action/innate/ability/A = new path
ability_actions[ability] = A
A.Grant(src)
/**
* Removes an ability from a source
*/
/mob/proc/remove_ability_from_source(list/abilities, source)
if(!islist(abilities))
abilities = list(abilities)
if(!length(innate_abilities))
return
for(var/ability in abilities)
if(!length(innate_abilities[ability]))
continue
innate_abilities[ability] -= source
if(!length(innate_abilities[ability]))
innate_abilities -= ability
qdel(ability_actions[ability])
ability_actions -= ability
+1 -1
View File
@@ -604,7 +604,7 @@
ENABLE_BITFIELD(combat_flags, COMBAT_FLAG_HARD_STAMCRIT)
filters += CIT_FILTER_STAMINACRIT
update_mobility()
if((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && total_health <= STAMINA_CRIT)
if((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && total_health <= STAMINA_CRIT_REMOVAL_THRESHOLD)
to_chat(src, "<span class='notice'>You don't feel nearly as exhausted anymore.</span>")
DISABLE_BITFIELD(combat_flags, COMBAT_FLAG_HARD_STAMCRIT)
filters -= CIT_FILTER_STAMINACRIT
@@ -100,8 +100,3 @@
if(dna.species.space_move(src))
return TRUE
return ..()
/mob/living/carbon/human/CanPass(atom/movable/mover, turf/target)
if(dna.species.species_pass_check())
return TRUE
return ..()
@@ -0,0 +1,112 @@
/datum/action/innate/ability/slime_blobform
name = "Puddle Transformation"
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "slimepuddle"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
required_mobility_flags = MOBILITY_STAND
var/is_puddle = FALSE
var/in_transformation_duration = 12
var/out_transformation_duration = 7
var/puddle_into_effect = /obj/effect/temp_visual/slime_puddle
var/puddle_from_effect = /obj/effect/temp_visual/slime_puddle/reverse
var/puddle_icon = 'icons/mob/mob.dmi'
var/puddle_state = "puddle"
var/mutable_appearance/tracked_overlay
var/datum/component/squeak/squeak
var/transforming = FALSE
var/last_use
/datum/action/innate/ability/slime_blobform/IsAvailable()
if(!transforming)
return ..()
else
return FALSE
/datum/action/innate/ability/slime_blobform/Remove(mob/M)
if(is_puddle)
detransform()
return ..()
/datum/action/innate/ability/slime_blobform/Activate()
var/mob/living/carbon/human/H = owner
//if they have anything stuck to their hands, we immediately say 'no' and return
for(var/obj/item/I in H.held_items)
if(HAS_TRAIT(I, TRAIT_NODROP))
to_chat(owner, "There's something stuck to your hand, stopping you from transforming!")
return
if(IsAvailable())
transforming = TRUE
UpdateButtonIcon()
var/mutcolor = owner.get_ability_property(INNATE_ABILITY_SLIME_BLOBFORM, PROPERTY_BLOBFORM_COLOR) || ("#" + H.dna.features["mcolor"])
if(!is_puddle)
if(CHECK_MOBILITY(H, MOBILITY_USE)) //if we can use items, we can turn into a puddle
is_puddle = TRUE //so we know which transformation to use when its used
ADD_TRAIT(H, TRAIT_HUMAN_NO_RENDER, SLIMEPUDDLE_TRAIT)
owner.cut_overlays() //we dont show our normal sprite, we show a puddle sprite
var/obj/effect/puddle_effect = new puddle_into_effect(get_turf(owner), owner.dir)
puddle_effect.color = mutcolor
H.Stun(in_transformation_duration, ignore_canstun = TRUE) //cant move while transforming
//series of traits that make up the puddle behaviour
ADD_TRAIT(H, TRAIT_PARALYSIS_L_ARM, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_PARALYSIS_R_ARM, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_MOBILITY_NOPICKUP, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_MOBILITY_NOUSE, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_SPRINT_LOCKED, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_COMBAT_MODE_LOCKED, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_MOBILITY_NOREST, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_ARMOR_BROKEN, SLIMEPUDDLE_TRAIT)
H.update_disabled_bodyparts(silent = TRUE) //silently update arms to be paralysed
H.add_movespeed_modifier(/datum/movespeed_modifier/slime_puddle)
H.layer -= 1 //go one layer down so people go over you
ENABLE_BITFIELD(H.pass_flags, PASSMOB) //this actually lets people pass over you
squeak = H.AddComponent(/datum/component/squeak, custom_sounds = list('sound/effects/blobattack.ogg')) //blorble noise when people step on you
//if the user is a changeling, retract their sting
H.unset_sting()
sleep(in_transformation_duration) //wait for animation to end
//set the puddle overlay up
var/mutable_appearance/puddle_overlay = mutable_appearance(icon = puddle_icon, icon_state = puddle_state)
puddle_overlay.color = mutcolor
tracked_overlay = puddle_overlay
owner.add_overlay(puddle_overlay)
transforming = FALSE
UpdateButtonIcon()
else
detransform()
else
to_chat(owner, "<span class='warning'>You need to be standing up to do this!") //just assume they're a slime because it's such a weird edgecase to have it and not be one (it shouldn't even be possible)
/datum/action/innate/ability/slime_blobform/proc/detransform()
var/mob/living/carbon/human/H = owner
//like the above, but reverse everything done!
H.cut_overlay(tracked_overlay)
var/obj/effect/puddle_effect = new puddle_from_effect(get_turf(owner), owner.dir)
puddle_effect.color = tracked_overlay.color
H.Stun(out_transformation_duration, ignore_canstun = TRUE)
sleep(out_transformation_duration)
REMOVE_TRAIT(H, TRAIT_PARALYSIS_L_ARM, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_PARALYSIS_R_ARM, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOPICKUP, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOUSE, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_SPRINT_LOCKED, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_COMBAT_MODE_LOCKED, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOREST, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_ARMOR_BROKEN, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_HUMAN_NO_RENDER, SLIMEPUDDLE_TRAIT)
H.update_disabled_bodyparts(silent = TRUE)
H.remove_movespeed_modifier(/datum/movespeed_modifier/slime_puddle)
H.layer += 1 //go one layer back above!
DISABLE_BITFIELD(H.pass_flags, PASSMOB)
is_puddle = FALSE
if(squeak)
squeak.RemoveComponent()
transforming = FALSE
UpdateButtonIcon()
@@ -0,0 +1,208 @@
/datum/action/innate/ability/humanoid_customization
name = "Alter Form"
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "alter_form" //placeholder
icon_icon = 'modular_citadel/icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
/datum/action/innate/ability/humanoid_customization/Activate()
if(owner.get_ability_property(INNATE_ABILITY_HUMANOID_CUSTOMIZATION, PROPERTY_CUSTOMIZATION_SILENT))
owner.visible_message("<span class='notice'>[owner] gains a look of \
concentration while standing perfectly still.\
Their body seems to shift and starts getting more goo-like.</span>",
"<span class='notice'>You focus intently on altering your body while \
standing perfectly still...</span>")
change_form()
///////
/////// NOTICE: This currently doens't support skin tone - if anyone wants to add this to non slimes, it's up to YOU to do this.
////// (someone should also add genital color switching, more mutant color selection)
///// maybe just make this entire thing tgui based. maybe.
///////
/datum/action/innate/ability/humanoid_customization/proc/change_form()
var/mob/living/carbon/human/H = owner
var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Body Color","Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel")
if(select_alteration == "Body Color")
var/new_color = input(owner, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null
if(new_color)
var/temp_hsv = RGBtoHSV(new_color)
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright
H.dna.features["mcolor"] = sanitize_hexcolor(new_color, 6)
H.update_body()
H.update_hair()
else
to_chat(H, "<span class='notice'>Invalid color. Your color is not bright enough.</span>")
else if(select_alteration == "Hair Style")
if(H.gender == MALE)
var/new_style = input(owner, "Select a facial hair style", "Hair Alterations") as null|anything in GLOB.facial_hair_styles_list
if(new_style)
H.facial_hair_style = new_style
else
H.facial_hair_style = "Shaved"
//handle normal hair
var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list
if(new_style)
H.hair_style = new_style
H.update_hair()
else if (select_alteration == "Genitals")
var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") in list("add sexual organ", "remove sexual organ", "cancel")
switch(operation)
if("add sexual organ")
var/new_organ = input("Select sexual organ:", "Organ Manipulation") as null|anything in GLOB.genitals_list
if(!new_organ)
return
H.give_genital(GLOB.genitals_list[new_organ])
if("remove sexual organ")
var/list/organs = list()
for(var/obj/item/organ/genital/X in H.internal_organs)
var/obj/item/organ/I = X
organs["[I.name] ([I.type])"] = I
var/obj/item/O = input("Select sexual organ:", "Organ Manipulation", null) as null|anything in organs
var/obj/item/organ/genital/G = organs[O]
if(!G)
return
G.forceMove(get_turf(H))
qdel(G)
H.update_genitals()
else if (select_alteration == "Ears")
var/list/snowflake_ears_list = list("Normal" = null)
for(var/path in GLOB.mam_ears_list)
var/datum/sprite_accessory/ears/mam_ears/instance = GLOB.mam_ears_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_ears_list[S.name] = path
var/new_ears
new_ears = input(owner, "Choose your character's ears:", "Ear Alteration") as null|anything in snowflake_ears_list
if(new_ears)
H.dna.features["mam_ears"] = new_ears
H.update_body()
else if (select_alteration == "Snout")
var/list/snowflake_snouts_list = list("Normal" = null)
for(var/path in GLOB.mam_snouts_list)
var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.mam_snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_snouts_list[S.name] = path
var/new_snout
new_snout = input(owner, "Choose your character's face:", "Face Alteration") as null|anything in snowflake_snouts_list
if(new_snout)
H.dna.features["mam_snouts"] = new_snout
H.update_body()
else if (select_alteration == "Markings")
var/list/snowflake_markings_list = list("None")
for(var/path in GLOB.mam_body_markings_list)
var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_markings_list[S.name] = path
var/new_mam_body_markings
new_mam_body_markings = input(H, "Choose your character's body markings:", "Marking Alteration") as null|anything in snowflake_markings_list
if(new_mam_body_markings)
H.dna.features["mam_body_markings"] = new_mam_body_markings
for(var/X in H.bodyparts) //propagates the markings changes
var/obj/item/bodypart/BP = X
BP.update_limb(FALSE, H)
H.update_body()
else if (select_alteration == "Tail")
var/list/snowflake_tails_list = list("Normal" = null)
for(var/path in GLOB.mam_tails_list)
var/datum/sprite_accessory/tails/mam_tails/instance = GLOB.mam_tails_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_tails_list[S.name] = path
var/new_tail
new_tail = input(owner, "Choose your character's Tail(s):", "Tail Alteration") as null|anything in snowflake_tails_list
if(new_tail)
H.dna.features["mam_tail"] = new_tail
if(new_tail != "None")
H.dna.features["taur"] = "None"
H.update_body()
else if (select_alteration == "Taur body")
var/list/snowflake_taur_list = list("Normal" = null)
for(var/path in GLOB.taur_list)
var/datum/sprite_accessory/taur/instance = GLOB.taur_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_taur_list[S.name] = path
var/new_taur
new_taur = input(owner, "Choose your character's tauric body:", "Tauric Alteration") as null|anything in snowflake_taur_list
if(new_taur)
H.dna.features["taur"] = new_taur
if(new_taur != "None")
H.dna.features["mam_tail"] = "None"
H.update_body()
else if (select_alteration == "Penis")
for(var/obj/item/organ/genital/penis/X in H.internal_organs)
qdel(X)
var/new_shape
new_shape = input(owner, "Choose your character's dong", "Genital Alteration") as null|anything in GLOB.cock_shapes_list
if(new_shape)
H.dna.features["cock_shape"] = new_shape
H.update_genitals()
H.give_genital(/obj/item/organ/genital/testicles)
H.give_genital(/obj/item/organ/genital/penis)
H.apply_overlay()
else if (select_alteration == "Vagina")
for(var/obj/item/organ/genital/vagina/X in H.internal_organs)
qdel(X)
var/new_shape
new_shape = input(owner, "Choose your character's pussy", "Genital Alteration") as null|anything in GLOB.vagina_shapes_list
if(new_shape)
H.dna.features["vag_shape"] = new_shape
H.update_genitals()
H.give_genital(/obj/item/organ/genital/womb)
H.give_genital(/obj/item/organ/genital/vagina)
H.apply_overlay()
else if (select_alteration == "Penis Length")
for(var/obj/item/organ/genital/penis/X in H.internal_organs)
qdel(X)
var/min_D = CONFIG_GET(number/penis_min_inches_prefs)
var/max_D = CONFIG_GET(number/penis_max_inches_prefs)
var/new_length = input(owner, "Penis length in inches:\n([min_D]-[max_D])", "Genital Alteration") as num|null
if(new_length)
H.dna.features["cock_length"] = clamp(round(new_length), min_D, max_D)
H.update_genitals()
H.apply_overlay()
H.give_genital(/obj/item/organ/genital/testicles)
H.give_genital(/obj/item/organ/genital/penis)
else if (select_alteration == "Breast Size")
for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
qdel(X)
var/new_size = input(owner, "Breast Size", "Genital Alteration") as null|anything in CONFIG_GET(keyed_list/breasts_cups_prefs)
if(new_size)
H.dna.features["breasts_size"] = new_size
H.update_genitals()
H.apply_overlay()
H.give_genital(/obj/item/organ/genital/breasts)
else if (select_alteration == "Breast Shape")
for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
qdel(X)
var/new_shape
new_shape = input(owner, "Breast Shape", "Genital Alteration") as null|anything in GLOB.breasts_shapes_list
if(new_shape)
H.dna.features["breasts_shape"] = new_shape
H.update_genitals()
H.apply_overlay()
H.give_genital(/obj/item/organ/genital/breasts)
else
return
@@ -0,0 +1,49 @@
/datum/action/innate/ability/limb_regrowth
name = "Regenerate Limbs"
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "slimeheal"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
required_mobility_flags = NONE
/datum/action/innate/ability/limb_regrowth/IsAvailable(silent = FALSE)
if(..())
var/mob/living/carbon/human/H = owner
var/list/limbs_to_heal = H.get_missing_limbs()
if(limbs_to_heal.len < 1)
return 0
var/mode = H.get_ability_property(INNATE_ABILITY_LIMB_REGROWTH, PROPERTY_LIMB_REGROWTH_USAGE_TYPE)
switch(mode)
if(REGROWTH_USES_BLOOD)
if(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
return TRUE
else
return FALSE
return 0
/datum/action/innate/ability/limb_regrowth/Activate()
var/mob/living/carbon/human/H = owner
var/list/limbs_to_heal = H.get_missing_limbs()
if(limbs_to_heal.len < 1)
to_chat(H, "<span class='notice'>You feel intact enough as it is.</span>")
return
to_chat(H, "<span class='notice'>You focus intently on your missing [limbs_to_heal.len >= 2 ? "limbs" : "limb"]...</span>")
var/mode = H.get_ability_property(INNATE_ABILITY_LIMB_REGROWTH, PROPERTY_LIMB_REGROWTH_USAGE_TYPE)
switch(mode)
if(REGROWTH_USES_BLOOD)
if(H.blood_volume >= 40*limbs_to_heal.len+(BLOOD_VOLUME_OKAY*H.blood_ratio))
H.regenerate_limbs()
H.blood_volume -= 40*limbs_to_heal.len
to_chat(H, "<span class='notice'>...and after a moment you finish reforming!</span>")
return
else if(H.blood_volume >= 40)//We can partially heal some limbs
while(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
var/healed_limb = pick(limbs_to_heal)
H.regenerate_limb(healed_limb)
limbs_to_heal -= healed_limb
H.blood_volume -= 40
to_chat(H, "<span class='warning'>...but there is not enough of you to fix everything! You must attain more mass to heal completely!</span>")
return
to_chat(H, "<span class='warning'>...but there is not enough of you to go around! You must attain more mass to heal!</span>")
@@ -2002,14 +2002,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(HAS_BONE in species_traits)
. |= BIO_JUST_BONE
//a check for if you should render any overlays or not
/datum/species/proc/should_render(mob/living/carbon/human/H)
return TRUE
//a check for if you want to forcibly make CanPass return TRUE for the mob with this species
/datum/species/proc/species_pass_check()
return FALSE
/////////////
//BREATHING//
/////////////
@@ -15,9 +15,6 @@
exotic_bloodtype = "GEL"
exotic_blood_color = "BLOOD_COLOR_SLIME"
damage_overlay_type = ""
var/datum/action/innate/regenerate_limbs/regenerate_limbs
var/datum/action/innate/slime_change/slime_change
var/datum/action/innate/slime_puddle/slime_puddle
liked_food = TOXIC | MEAT
disliked_food = null
toxic_food = ANTITOXIC
@@ -37,27 +34,19 @@
icon_state = "brain-slime"
/datum/species/jelly/on_species_loss(mob/living/carbon/C)
if(slime_puddle && slime_puddle.is_puddle)
slime_puddle.Activate()
if(regenerate_limbs)
regenerate_limbs.Remove(C)
if(slime_change)
slime_change.Remove(C)
if(slime_puddle)
slime_puddle.Remove(C)
C.faction -= "slime"
if(ishuman(C))
var/mob/living/carbon/human/H = C
H.remove_ability_from_source(list(INNATE_ABILITY_SLIME_BLOBFORM, INNATE_ABILITY_LIMB_REGROWTH, INNATE_ABILITY_HUMANOID_CUSTOMIZATION), ABILITY_SOURCE_SPECIES)
..()
C.faction -= "slime"
/datum/species/jelly/on_species_gain(mob/living/carbon/C, datum/species/old_species)
..()
if(ishuman(C))
regenerate_limbs = new
regenerate_limbs.Grant(C)
slime_change = new
slime_change.Grant(C)
slime_puddle = new
slime_puddle.Grant(C)
var/mob/living/carbon/human/H = C
H.grant_ability_from_source(list(INNATE_ABILITY_SLIME_BLOBFORM, INNATE_ABILITY_LIMB_REGROWTH, INNATE_ABILITY_HUMANOID_CUSTOMIZATION), ABILITY_SOURCE_SPECIES)
H.set_ability_property(INNATE_ABILITY_LIMB_REGROWTH, PROPERTY_LIMB_REGROWTH_USAGE_TYPE, REGROWTH_USES_BLOOD)
C.faction |= "slime"
/datum/species/jelly/handle_body(mob/living/carbon/human/H)
@@ -65,18 +54,6 @@
//update blood color to body color
exotic_blood_color = "#" + H.dna.features["mcolor"]
/datum/species/jelly/should_render()
if(slime_puddle && slime_puddle.is_puddle)
return FALSE
else
return ..()
/datum/species/jelly/species_pass_check()
if(slime_puddle && slime_puddle.is_puddle)
return TRUE
else
return ..()
/datum/species/jelly/spec_life(mob/living/carbon/human/H)
if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_NOMARROW)) //can't farm slime jelly from a dead slime/jelly person indefinitely, and no regeneration for blooduskers
return
@@ -94,8 +71,6 @@
to_chat(H, "<span class='danger'>You feel drained!</span>")
if(H.blood_volume < (BLOOD_VOLUME_BAD*H.blood_ratio))
Cannibalize_Body(H)
if(regenerate_limbs)
regenerate_limbs.UpdateButtonIcon()
/datum/species/jelly/proc/Cannibalize_Body(mob/living/carbon/human/H)
var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs()
@@ -111,47 +86,6 @@
qdel(consumed_limb)
H.blood_volume += 20
/datum/action/innate/regenerate_limbs
name = "Regenerate Limbs"
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "slimeheal"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
required_mobility_flags = NONE
/datum/action/innate/regenerate_limbs/IsAvailable(silent = FALSE)
if(..())
var/mob/living/carbon/human/H = owner
var/list/limbs_to_heal = H.get_missing_limbs()
if(limbs_to_heal.len < 1)
return 0
if(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
return 1
return 0
/datum/action/innate/regenerate_limbs/Activate()
var/mob/living/carbon/human/H = owner
var/list/limbs_to_heal = H.get_missing_limbs()
if(limbs_to_heal.len < 1)
to_chat(H, "<span class='notice'>You feel intact enough as it is.</span>")
return
to_chat(H, "<span class='notice'>You focus intently on your missing [limbs_to_heal.len >= 2 ? "limbs" : "limb"]...</span>")
if(H.blood_volume >= 40*limbs_to_heal.len+(BLOOD_VOLUME_OKAY*H.blood_ratio))
H.regenerate_limbs()
H.blood_volume -= 40*limbs_to_heal.len
to_chat(H, "<span class='notice'>...and after a moment you finish reforming!</span>")
return
else if(H.blood_volume >= 40)//We can partially heal some limbs
while(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
var/healed_limb = pick(limbs_to_heal)
H.regenerate_limb(healed_limb)
limbs_to_heal -= healed_limb
H.blood_volume -= 40
to_chat(H, "<span class='warning'>...but there is not enough of you to fix everything! You must attain more mass to heal completely!</span>")
return
to_chat(H, "<span class='warning'>...but there is not enough of you to go around! You must attain more mass to heal!</span>")
////////////////////////////////////////////////////////SLIMEPEOPLE///////////////////////////////////////////////////////////////////
//Slime people are able to split like slimes, retaining a single mind that can swap between bodies at will, even after death.
@@ -482,314 +416,6 @@
allowed_limb_ids = list(SPECIES_SLIME,SPECIES_STARGAZER,SPECIES_SLIME_LUMI)
/datum/action/innate/slime_change
name = "Alter Form"
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "alter_form" //placeholder
icon_icon = 'modular_citadel/icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
/datum/action/innate/slime_change/Activate()
var/mob/living/carbon/human/H = owner
if(!isjellyperson(H))
return
else
H.visible_message("<span class='notice'>[owner] gains a look of \
concentration while standing perfectly still.\
Their body seems to shift and starts getting more goo-like.</span>",
"<span class='notice'>You focus intently on altering your body while \
standing perfectly still...</span>")
change_form()
/datum/action/innate/slime_change/proc/change_form()
var/mob/living/carbon/human/H = owner
var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Body Color","Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel")
if(select_alteration == "Body Color")
var/new_color = input(owner, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null
if(new_color)
var/temp_hsv = RGBtoHSV(new_color)
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright
H.dna.features["mcolor"] = sanitize_hexcolor(new_color, 6)
H.update_body()
H.update_hair()
else
to_chat(H, "<span class='notice'>Invalid color. Your color is not bright enough.</span>")
else if(select_alteration == "Hair Style")
if(H.gender == MALE)
var/new_style = input(owner, "Select a facial hair style", "Hair Alterations") as null|anything in GLOB.facial_hair_styles_list
if(new_style)
H.facial_hair_style = new_style
else
H.facial_hair_style = "Shaved"
//handle normal hair
var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list
if(new_style)
H.hair_style = new_style
H.update_hair()
else if (select_alteration == "Genitals")
var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") in list("add sexual organ", "remove sexual organ", "cancel")
switch(operation)
if("add sexual organ")
var/new_organ = input("Select sexual organ:", "Organ Manipulation") as null|anything in GLOB.genitals_list
if(!new_organ)
return
H.give_genital(GLOB.genitals_list[new_organ])
if("remove sexual organ")
var/list/organs = list()
for(var/obj/item/organ/genital/X in H.internal_organs)
var/obj/item/organ/I = X
organs["[I.name] ([I.type])"] = I
var/obj/item/O = input("Select sexual organ:", "Organ Manipulation", null) as null|anything in organs
var/obj/item/organ/genital/G = organs[O]
if(!G)
return
G.forceMove(get_turf(H))
qdel(G)
H.update_genitals()
else if (select_alteration == "Ears")
var/list/snowflake_ears_list = list("Normal" = null)
for(var/path in GLOB.mam_ears_list)
var/datum/sprite_accessory/ears/mam_ears/instance = GLOB.mam_ears_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_ears_list[S.name] = path
var/new_ears
new_ears = input(owner, "Choose your character's ears:", "Ear Alteration") as null|anything in snowflake_ears_list
if(new_ears)
H.dna.features["mam_ears"] = new_ears
H.update_body()
else if (select_alteration == "Snout")
var/list/snowflake_snouts_list = list("Normal" = null)
for(var/path in GLOB.mam_snouts_list)
var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.mam_snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_snouts_list[S.name] = path
var/new_snout
new_snout = input(owner, "Choose your character's face:", "Face Alteration") as null|anything in snowflake_snouts_list
if(new_snout)
H.dna.features["mam_snouts"] = new_snout
H.update_body()
else if (select_alteration == "Markings")
var/list/snowflake_markings_list = list("None")
for(var/path in GLOB.mam_body_markings_list)
var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_markings_list[S.name] = path
var/new_mam_body_markings
new_mam_body_markings = input(H, "Choose your character's body markings:", "Marking Alteration") as null|anything in snowflake_markings_list
if(new_mam_body_markings)
H.dna.features["mam_body_markings"] = new_mam_body_markings
for(var/X in H.bodyparts) //propagates the markings changes
var/obj/item/bodypart/BP = X
BP.update_limb(FALSE, H)
H.update_body()
else if (select_alteration == "Tail")
var/list/snowflake_tails_list = list("Normal" = null)
for(var/path in GLOB.mam_tails_list)
var/datum/sprite_accessory/tails/mam_tails/instance = GLOB.mam_tails_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_tails_list[S.name] = path
var/new_tail
new_tail = input(owner, "Choose your character's Tail(s):", "Tail Alteration") as null|anything in snowflake_tails_list
if(new_tail)
H.dna.features["mam_tail"] = new_tail
if(new_tail != "None")
H.dna.features["taur"] = "None"
H.update_body()
else if (select_alteration == "Taur body")
var/list/snowflake_taur_list = list("Normal" = null)
for(var/path in GLOB.taur_list)
var/datum/sprite_accessory/taur/instance = GLOB.taur_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
snowflake_taur_list[S.name] = path
var/new_taur
new_taur = input(owner, "Choose your character's tauric body:", "Tauric Alteration") as null|anything in snowflake_taur_list
if(new_taur)
H.dna.features["taur"] = new_taur
if(new_taur != "None")
H.dna.features["mam_tail"] = "None"
H.update_body()
else if (select_alteration == "Penis")
for(var/obj/item/organ/genital/penis/X in H.internal_organs)
qdel(X)
var/new_shape
new_shape = input(owner, "Choose your character's dong", "Genital Alteration") as null|anything in GLOB.cock_shapes_list
if(new_shape)
H.dna.features["cock_shape"] = new_shape
H.update_genitals()
H.give_genital(/obj/item/organ/genital/testicles)
H.give_genital(/obj/item/organ/genital/penis)
H.apply_overlay()
else if (select_alteration == "Vagina")
for(var/obj/item/organ/genital/vagina/X in H.internal_organs)
qdel(X)
var/new_shape
new_shape = input(owner, "Choose your character's pussy", "Genital Alteration") as null|anything in GLOB.vagina_shapes_list
if(new_shape)
H.dna.features["vag_shape"] = new_shape
H.update_genitals()
H.give_genital(/obj/item/organ/genital/womb)
H.give_genital(/obj/item/organ/genital/vagina)
H.apply_overlay()
else if (select_alteration == "Penis Length")
for(var/obj/item/organ/genital/penis/X in H.internal_organs)
qdel(X)
var/min_D = CONFIG_GET(number/penis_min_inches_prefs)
var/max_D = CONFIG_GET(number/penis_max_inches_prefs)
var/new_length = input(owner, "Penis length in inches:\n([min_D]-[max_D])", "Genital Alteration") as num|null
if(new_length)
H.dna.features["cock_length"] = clamp(round(new_length), min_D, max_D)
H.update_genitals()
H.apply_overlay()
H.give_genital(/obj/item/organ/genital/testicles)
H.give_genital(/obj/item/organ/genital/penis)
else if (select_alteration == "Breast Size")
for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
qdel(X)
var/new_size = input(owner, "Breast Size", "Genital Alteration") as null|anything in CONFIG_GET(keyed_list/breasts_cups_prefs)
if(new_size)
H.dna.features["breasts_size"] = new_size
H.update_genitals()
H.apply_overlay()
H.give_genital(/obj/item/organ/genital/breasts)
else if (select_alteration == "Breast Shape")
for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
qdel(X)
var/new_shape
new_shape = input(owner, "Breast Shape", "Genital Alteration") as null|anything in GLOB.breasts_shapes_list
if(new_shape)
H.dna.features["breasts_shape"] = new_shape
H.update_genitals()
H.apply_overlay()
H.give_genital(/obj/item/organ/genital/breasts)
else
return
/datum/action/innate/slime_puddle
name = "Puddle Transformation"
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "slimepuddle"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
required_mobility_flags = MOBILITY_STAND
var/is_puddle = FALSE
var/in_transformation_duration = 12
var/out_transformation_duration = 7
var/puddle_into_effect = /obj/effect/temp_visual/slime_puddle
var/puddle_from_effect = /obj/effect/temp_visual/slime_puddle/reverse
var/puddle_icon = 'icons/mob/mob.dmi'
var/puddle_state = "puddle"
var/tracked_overlay
var/datum/component/squeak/squeak
var/transforming = FALSE
var/last_use
/datum/action/innate/slime_puddle/IsAvailable()
if(!transforming)
return ..()
else
return FALSE
/datum/action/innate/slime_puddle/Activate()
var/mob/living/carbon/human/H = owner
//if they have anything stuck to their hands, we immediately say 'no' and return
for(var/obj/item/I in H.held_items)
if(HAS_TRAIT(I, TRAIT_NODROP))
to_chat(owner, "There's something stuck to your hand, stopping you from transforming!")
return
if(isjellyperson(owner) && IsAvailable())
transforming = TRUE
UpdateButtonIcon()
var/mutcolor = "#" + H.dna.features["mcolor"]
if(!is_puddle)
if(CHECK_MOBILITY(H, MOBILITY_USE)) //if we can use items, we can turn into a puddle
is_puddle = TRUE //so we know which transformation to use when its used
owner.cut_overlays() //we dont show our normal sprite, we show a puddle sprite
var/obj/effect/puddle_effect = new puddle_into_effect(get_turf(owner), owner.dir)
puddle_effect.color = mutcolor
H.Stun(in_transformation_duration, ignore_canstun = TRUE) //cant move while transforming
//series of traits that make up the puddle behaviour
ADD_TRAIT(H, TRAIT_PARALYSIS_L_ARM, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_PARALYSIS_R_ARM, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_MOBILITY_NOPICKUP, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_MOBILITY_NOUSE, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_SPRINT_LOCKED, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_COMBAT_MODE_LOCKED, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_MOBILITY_NOREST, SLIMEPUDDLE_TRAIT)
ADD_TRAIT(H, TRAIT_ARMOR_BROKEN, SLIMEPUDDLE_TRAIT)
H.update_disabled_bodyparts(silent = TRUE) //silently update arms to be paralysed
H.add_movespeed_modifier(/datum/movespeed_modifier/slime_puddle)
H.layer -= 1 //go one layer down so people go over you
ENABLE_BITFIELD(H.pass_flags, PASSMOB) //this actually lets people pass over you
squeak = H.AddComponent(/datum/component/squeak, custom_sounds = list('sound/effects/blobattack.ogg')) //blorble noise when people step on you
//if the user is a changeling, retract their sting
H.unset_sting()
sleep(in_transformation_duration) //wait for animation to end
//set the puddle overlay up
var/mutable_appearance/puddle_overlay = mutable_appearance(icon = puddle_icon, icon_state = puddle_state)
puddle_overlay.color = mutcolor
tracked_overlay = puddle_overlay
owner.add_overlay(puddle_overlay)
transforming = FALSE
UpdateButtonIcon()
else
//like the above, but reverse everything done!
owner.cut_overlay(tracked_overlay)
var/obj/effect/puddle_effect = new puddle_from_effect(get_turf(owner), owner.dir)
puddle_effect.color = mutcolor
H.Stun(out_transformation_duration, ignore_canstun = TRUE)
sleep(out_transformation_duration)
REMOVE_TRAIT(H, TRAIT_PARALYSIS_L_ARM, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_PARALYSIS_R_ARM, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOPICKUP, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOUSE, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_SPRINT_LOCKED, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_COMBAT_MODE_LOCKED, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOREST, SLIMEPUDDLE_TRAIT)
REMOVE_TRAIT(H, TRAIT_ARMOR_BROKEN, SLIMEPUDDLE_TRAIT)
H.update_disabled_bodyparts(silent = TRUE)
H.remove_movespeed_modifier(/datum/movespeed_modifier/slime_puddle)
H.layer += 1 //go one layer back above!
DISABLE_BITFIELD(H.pass_flags, PASSMOB)
is_puddle = FALSE
if(squeak)
squeak.RemoveComponent()
owner.regenerate_icons()
transforming = FALSE
UpdateButtonIcon()
else
to_chat(owner, "<span class='warning'>You need to be standing up to do this!") //just assume they're a slime because it's such a weird edgecase to have it and not be one (it shouldn't even be possible)
///////////////////////////////////LUMINESCENTS//////////////////////////////////////////
//Luminescents are able to consume and use slime extracts, without them decaying.
@@ -48,18 +48,22 @@ There are several things that need to be remembered:
*/
/mob/living/carbon/human/ComponentInitialize()
. = ..()
RegisterSignal(src, SIGNAL_TRAIT(TRAIT_HUMAN_NO_RENDER), /mob.proc/regenerate_icons)
//HAIR OVERLAY
/mob/living/carbon/human/update_hair()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
dna.species.handle_hair(src)
//used when putting/removing clothes that hide certain mutant body parts to just update those and not update the whole body.
/mob/living/carbon/human/proc/update_mutant_bodyparts()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
dna.species.handle_mutant_bodyparts(src)
/mob/living/carbon/human/update_body(update_genitals = FALSE)
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(BODY_LAYER)
dna.species.handle_body(src)
..()
@@ -69,11 +73,10 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_fire()
..((fire_stacks > 3) ? "Standing" : "Generic_mob_burning")
/* --------------------------------------- */
//For legacy support.
/mob/living/carbon/human/regenerate_icons()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
if(!..())
icon_render_key = null //invalidate bodyparts cache
update_body(TRUE)
@@ -102,7 +105,7 @@ There are several things that need to be remembered:
//vvvvvv UPDATE_INV PROCS vvvvvv
/mob/living/carbon/human/update_inv_w_uniform()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(UNIFORM_LAYER)
if(client && hud_used)
@@ -154,7 +157,7 @@ There are several things that need to be remembered:
update_mutant_bodyparts()
/mob/living/carbon/human/update_inv_wear_id()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(ID_LAYER)
if(client && hud_used)
@@ -179,7 +182,7 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_gloves()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(GLOVES_LAYER)
if(client && hud_used && hud_used.inv_slots[SLOT_GLOVES])
@@ -213,7 +216,7 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_glasses()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(GLASSES_LAYER)
if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated
@@ -240,7 +243,7 @@ There are several things that need to be remembered:
apply_overlay(GLASSES_LAYER)
/mob/living/carbon/human/update_inv_ears()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(EARS_LAYER)
if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated
@@ -266,7 +269,7 @@ There are several things that need to be remembered:
apply_overlay(EARS_LAYER)
/mob/living/carbon/human/update_inv_shoes()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(SHOES_LAYER)
if(get_num_legs(FALSE) <2)
@@ -304,7 +307,7 @@ There are several things that need to be remembered:
apply_overlay(SHOES_LAYER)
/mob/living/carbon/human/update_inv_s_store()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(SUIT_STORE_LAYER)
if(client && hud_used)
@@ -328,7 +331,7 @@ There are several things that need to be remembered:
apply_overlay(SUIT_STORE_LAYER)
/mob/living/carbon/human/update_inv_head()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(HEAD_LAYER)
if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
@@ -368,7 +371,7 @@ There are several things that need to be remembered:
update_mutant_bodyparts()
/mob/living/carbon/human/update_inv_belt()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(BELT_LAYER)
if(client && hud_used)
@@ -390,7 +393,7 @@ There are several things that need to be remembered:
apply_overlay(BELT_LAYER)
/mob/living/carbon/human/update_inv_wear_suit()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(SUIT_LAYER)
if(client && hud_used)
@@ -477,7 +480,7 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_wear_mask()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
remove_overlay(FACEMASK_LAYER)
if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
@@ -518,7 +521,7 @@ There are several things that need to be remembered:
update_mutant_bodyparts() //e.g. upgate needed because mask now hides lizard snout
/mob/living/carbon/human/update_inv_back()
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
..()
var/mutable_appearance/back_overlay = overlays_standing[BACK_LAYER]
if(back_overlay)
@@ -747,7 +750,7 @@ use_mob_overlay_icon: if FALSE, it will always use the default_icon_file even if
if(!dna.species)
return
if(dna.species.should_render())
if(!HAS_TRAIT(src, TRAIT_HUMAN_NO_RENDER))
return
var/obj/item/bodypart/HD = get_bodypart("head")
+1
View File
@@ -34,6 +34,7 @@
ranged_ability.remove_ranged_ability(src)
if(buckled)
buckled.unbuckle_mob(src,force=1)
QDEL_LIST_ASSOC_VAL(ability_actions)
remove_from_all_data_huds()
GLOB.mob_living_list -= src
+2 -1
View File
@@ -6,6 +6,7 @@
RegisterSignal(src, SIGNAL_TRAIT(TRAIT_MOBILITY_NOPICKUP), .proc/update_mobility)
RegisterSignal(src, SIGNAL_TRAIT(TRAIT_MOBILITY_NOUSE), .proc/update_mobility)
RegisterSignal(src, SIGNAL_TRAIT(TRAIT_MOBILITY_NOREST), .proc/update_mobility)
RegisterSignal(src, SIGNAL_TRAIT(TRAIT_LIVING_NO_DENSITY), .proc/update_density)
//Stuff like mobility flag updates, resting updates, etc.
@@ -148,7 +149,7 @@
L.update_pull_movespeed()
//Handle lying down, voluntary or involuntary
density = !lying
update_density()
if(lying)
set_resting(TRUE, TRUE, FALSE)
if(layer == initial(layer)) //to avoid special cases like hiding larvas.
@@ -17,6 +17,9 @@
pixel_x = get_standard_pixel_x_offset(lying)
pixel_y = get_standard_pixel_y_offset(lying)
/mob/living/proc/update_density()
density = !lying && !HAS_TRAIT(src, TRAIT_LIVING_NO_DENSITY)
/mob/living/CanPass(atom/movable/mover, turf/target)
if((mover.pass_flags & PASSMOB))
return TRUE
+9 -1
View File
@@ -11,7 +11,7 @@
blocks_emissive = EMISSIVE_BLOCK_GENERIC
vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of mob in openspace.
attack_hand_is_action = TRUE
attack_hand_unwieldlyness = CLICK_CD_MELEE
attack_hand_speed = 0
@@ -169,3 +169,11 @@
var/typing_indicator_timerid
/// Current state of our typing indicator. Used for cut overlay, DO NOT RUNTIME ASSIGN OTHER THAN FROM SHOW/CLEAR. Used to absolutely ensure we do not get stuck overlays.
var/mutable_appearance/typing_indicator_current
/// Ability system based on action buttons. Can be ported to base /mob or /mob/living later if needed, easily - the procs are currently on living/carbon/human/innate_abilities.dm
/// datum traits-style lazylist of abilities
var/list/innate_abilities
/// ability = action button instance.
var/list/ability_actions
/// ability = list(data). see __DEFINES/mobs/innate_abilities.dm
var/list/ability_properties
@@ -113,7 +113,7 @@
/datum/design/nanites/spreading
name = "Infective Exo-Locomotion"
desc = "The nanites gain the ability to survive for brief periods outside of the human body, as well as the ability to start new colonies without an integration process; \
resulting in an extremely infective strain of nanites."
resulting in an extremely infective strain of nanites. Bypasses antiviral defense"
id = "spreading_nanites"
program_type = /datum/nanite_program/spreading
category = list("Utility Nanites")
@@ -133,6 +133,21 @@
program_type = /datum/nanite_program/mitosis
category = list("Utility Nanites")
/datum/design/nanites/antiviral
name = "Enhanced Error Correction"
desc = "The nanites self-propagate and replicate their program storage memory, preventing viral takeovers."
id = "antiviral_nanites"
program_type = /datum/nanite_program/lockout/antiviral
category = list("Utility Nanites")
/datum/design/nanites/hostile_lockdown
name = "Hostile Lockdown"
desc = "The nanites constantly encrypt and scramble their own control sectors, preventing consoles from controlling them. Furthermore, \
if the host happens to be a synthetic organism with innate control over nanite strains, this will prevent them from acting on the nanites as well."
id = "hostile_lockdown"
program_type = /datum/nanite_program/lockout/hostile_lockdown
category = list("Utility Nanites")
////////////////////MEDICAL NANITES//////////////////////////////////////
/datum/design/nanites/regenerative
name = "Accelerated Regeneration"
@@ -41,12 +41,12 @@
update_icon()
/obj/machinery/nanite_chamber/proc/set_safety(threshold)
if(!occupant)
if(!occupant || SEND_SIGNAL(occupant, COMSIG_NANITE_CHECK_CONSOLE_LOCK))
return
SEND_SIGNAL(occupant, COMSIG_NANITE_SET_SAFETY, threshold)
/obj/machinery/nanite_chamber/proc/set_cloud(cloud_id)
if(!occupant)
if(!occupant || SEND_SIGNAL(occupant, COMSIG_NANITE_CHECK_CONSOLE_LOCK))
return
SEND_SIGNAL(occupant, COMSIG_NANITE_SET_CLOUD, cloud_id)
@@ -82,7 +82,7 @@
return
if((stat & MAINT) || panel_open)
return
if(!occupant || busy)
if(!occupant || busy || SEND_SIGNAL(occupant, COMSIG_NANITE_CHECK_CONSOLE_LOCK))
return
var/locked_state = locked
@@ -173,7 +173,6 @@
return FALSE
..()
return TRUE
/obj/machinery/nanite_chamber/relaymove(mob/user as mob)
@@ -50,6 +50,10 @@
data["status_msg"] = chamber.busy_message
return data
if(SEND_SIGNAL(L, COMSIG_NANITE_CHECK_CONSOLE_LOCK))
data["status_msg"] = "Error: Nanite keycodes scrambled. Unable to operate."
return data
data["status_msg"] = null
data["scan_level"] = chamber.scan_level
data["locked"] = chamber.locked
@@ -54,6 +54,13 @@
//Rules that automatically manage if the program's active without requiring separate sensor programs
var/list/datum/nanite_rule/rules = list()
/// Corruptable - able to have code/configuration changed
var/corruptable = TRUE
/// error flicking - able to be randomly toggled by errors
var/error_flicking = TRUE
/// immutable - cannot be overwritten by other programs
var/immutable = FALSE
/datum/nanite_program/New()
. = ..()
register_extra_settings()
@@ -68,8 +75,15 @@
on_mob_remove()
if(nanites)
nanites.programs -= src
nanites.permanent_programs -= src
return ..()
/**
* Checks if we're a permanent program
*/
/datum/nanite_program/proc/is_permanent()
return nanites && (src in nanites.permanent_programs)
/datum/nanite_program/proc/copy()
var/datum/nanite_program/new_program = new type()
copy_programming(new_program, TRUE)
@@ -77,6 +91,8 @@
return new_program
/datum/nanite_program/proc/copy_programming(datum/nanite_program/target, copy_activated = TRUE)
if(target.immutable)
return
if(copy_activated)
target.activated = activated
target.timer_restart = timer_restart
@@ -234,7 +250,7 @@
/datum/nanite_program/proc/on_emp(severity)
if(program_flags & NANITE_EMP_IMMUNE)
return
if(prob(80 / severity))
if(prob(severity / 2))
software_error()
/datum/nanite_program/proc/on_shock(shock_damage)
@@ -242,7 +258,7 @@
if(prob(10))
software_error()
else if(prob(33))
qdel(src)
self_destruct()
/datum/nanite_program/proc/on_minor_shock()
if(!program_flags & NANITE_SHOCK_IMMUNE)
@@ -254,26 +270,29 @@
/datum/nanite_program/proc/software_error(type)
if(!type)
type = rand(1,5)
type = rand(1,is_permanent()? 4 : 5)
switch(type)
if(1)
qdel(src) //kill switch
self_destruct() //kill switch
return
if(2) //deprogram codes
activation_code = 0
deactivation_code = 0
kill_code = 0
trigger_code = 0
if(corruptable)
activation_code = 0
deactivation_code = 0
kill_code = 0
trigger_code = 0
if(3)
toggle() //enable/disable
if(error_flicking)
toggle() //enable/disable
if(4)
if(can_trigger)
if(error_flicking && can_trigger)
trigger()
if(5) //Program is scrambled and does something different
var/rogue_type = pick(rogue_types)
var/datum/nanite_program/rogue = new rogue_type
nanites.add_program(null, rogue, src)
qdel(src)
if(corruptable)
var/rogue_type = pick(rogue_types)
var/datum/nanite_program/rogue = new rogue_type
nanites.add_program(null, rogue, src)
self_destruct()
/datum/nanite_program/proc/receive_signal(code, source)
if(activation_code && code == activation_code && !activated)
@@ -285,10 +304,18 @@
if(can_trigger && trigger_code && code == trigger_code)
trigger()
host_mob.investigate_log("'s [name] nanite program was triggered by [source] with code [code].", INVESTIGATE_NANITES)
if(kill_code && code == kill_code)
if((kill_code && code == kill_code) && !is_permanent())
host_mob.investigate_log("'s [name] nanite program was deleted by [source] with code [code].", INVESTIGATE_NANITES)
qdel(src)
/**
* Attempts to destroy ourselves
*/
/datum/nanite_program/proc/self_destruct()
if(is_permanent())
return
qdel(src)
///A nanite program containing a behaviour protocol. Only one protocol of each class can be active at once.
//Moved to being 'normally' researched due to lack of B.E.P.I.S.
/datum/nanite_program/protocol
@@ -11,7 +11,7 @@
return FALSE
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
var/list/parts = C.get_damaged_bodyparts(TRUE,TRUE, status = list(BODYPART_ORGANIC))
var/list/parts = C.get_damaged_bodyparts(TRUE,TRUE, status = list(BODYPART_ORGANIC, BODYPART_NANITES))
if(!parts.len)
return FALSE
return ..()
@@ -19,7 +19,7 @@
/datum/nanite_program/regenerative/active_effect()
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
var/list/parts = C.get_damaged_bodyparts(TRUE,TRUE, status = list(BODYPART_ORGANIC))
var/list/parts = C.get_damaged_bodyparts(TRUE,TRUE, status = list(BODYPART_ORGANIC, BODYPART_NANITES))
if(!parts.len)
return
for(var/obj/item/bodypart/L in parts)
@@ -121,7 +121,7 @@
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
var/list/parts = C.get_damaged_bodyparts(TRUE, TRUE, status = list(BODYPART_ROBOTIC, BODYPART_HYBRID))
var/list/parts = C.get_damaged_bodyparts(TRUE, TRUE, status = list(BODYPART_ROBOTIC, BODYPART_HYBRID, BODYPART_NANITES))
if(!parts.len)
return FALSE
else
@@ -132,7 +132,7 @@
/datum/nanite_program/repairing/active_effect(mob/living/M)
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
var/list/parts = C.get_damaged_bodyparts(TRUE, TRUE, status = list(BODYPART_ROBOTIC, BODYPART_HYBRID))
var/list/parts = C.get_damaged_bodyparts(TRUE, TRUE, status = list(BODYPART_ROBOTIC, BODYPART_HYBRID, BODYPART_NANITES))
if(!parts.len)
return
var/update = FALSE
@@ -176,12 +176,12 @@
/datum/nanite_program/regenerative_advanced/active_effect()
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
var/list/parts = C.get_damaged_bodyparts(TRUE,TRUE, status = list(BODYPART_ORGANIC))
var/list/parts = C.get_damaged_bodyparts(TRUE,TRUE, status = list(BODYPART_ORGANIC, BODYPART_NANITES))
if(!parts.len)
return
var/update = FALSE
for(var/obj/item/bodypart/L in parts)
if(L.heal_damage(3/parts.len, 3/parts.len, FALSE))
if(L.heal_damage(3/parts.len, 3/parts.len, 0))
update = TRUE
if(update)
host_mob.update_damage_overlays()
@@ -16,7 +16,7 @@
var/datum/nanite_extra_setting/program = extra_settings[NES_PROGRAM_OVERWRITE]
var/datum/nanite_extra_setting/cloud = extra_settings[NES_CLOUD_OVERWRITE]
for(var/mob/M in orange(host_mob, 5))
if(SEND_SIGNAL(M, COMSIG_NANITE_IS_STEALTHY))
if(SEND_SIGNAL(M, COMSIG_NANITE_CHECK_VIRAL_PREVENTION))
continue
switch(program.get_value())
if("Overwrite")
@@ -350,3 +350,66 @@
/datum/action/innate/nanite_button/Activate()
program.press()
/datum/nanite_program/lockout
unique = TRUE
var/emp_disable_time = 0
var/shock_disable_time = 0
var/minor_shock_disable_time = 0
var/disable_time = 0
var/lock_console = FALSE
var/lock_virus = FALSE
var/lock_host = FALSE
/datum/nanite_program/lockout/enable_passive_effect()
. = ..()
if(lock_console)
RegisterSignal(src, COMSIG_NANITE_INTERNAL_CONSOLE_LOCK_CHECK, .proc/check_antivirus)
if(lock_host)
RegisterSignal(src, COMSIG_NANITE_INTERNAL_HOST_LOCK_CHECK, .proc/check_antivirus)
if(lock_virus)
RegisterSignal(src, COMSIG_NANITE_INTERNAL_VIRAL_PREVENTION_CHECK, .proc/check_antivirus)
/datum/nanite_program/lockout/disable_passive_effect()
. = ..()
UnregisterSignal(src, list(
COMSIG_NANITE_INTERNAL_HOST_LOCK_CHECK,
COMSIG_NANITE_INTERNAL_CONSOLE_LOCK_CHECK,
COMSIG_NANITE_INTERNAL_VIRAL_PREVENTION_CHECK
))
/datum/nanite_program/lockout/on_emp(severity)
// no parent call on purpose
disable_time = max(disable_time, world.time + emp_disable_time)
/datum/nanite_program/lockout/on_shock(shock_damage)
// no parent call on purpose
disable_time = max(disable_time, world.time + shock_disable_time)
/datum/nanite_program/lockout/on_minor_shock()
// no parent call on purpose
disable_time = max(disable_time, world.time + minor_shock_disable_time)
/datum/nanite_program/lockout/proc/check_antivirus()
return (world.time <= disable_time)? NANITE_CHANGES_LOCKED : NONE
/datum/nanite_program/lockout/antiviral
name = "Enhanced Error Correction"
desc = "Through expensive CRC checking and replication, prevents viral takeover of the nanite strain's control sectors. \
Temporarily disabled by EMPs and shocks."
use_rate = 0.5
emp_disable_time = 3 MINUTES
shock_disable_time = 45 SECONDS
minor_shock_disable_time = 10 SECONDS
lock_virus = TRUE
/datum/nanite_program/lockout/hostile_lockdown
name = "Hostile Lockdown"
desc = "Constantly encrypts and scrambles the nanites' control memory, preventing consoles from modifying them and also locking out conscious host control of the nanite strain, if the host happens to be capable of that. \
Temporarily disabled by EMPs and shocks."
use_rate = 0.5
emp_disable_time = 3 MINUTES
shock_disable_time = 1 MINUTES
minor_shock_disable_time = 15 SECONDS
lock_host = TRUE
lock_console = TRUE
@@ -91,7 +91,7 @@
var/heavy_range = FLOOR(nanite_amount/100, 1) - 1
var/light_range = FLOOR(nanite_amount/50, 1) - 1
explosion(host_mob, 0, heavy_range, light_range)
qdel(nanites)
nanites.delete_nanites()
//TODO make it defuse if triggered again
@@ -23,7 +23,7 @@
display_name = "Mesh Nanite Programming"
description = "Nanite programs that require static structures and membranes."
prereq_ids = list("nanite_base","engineering")
design_ids = list("hardening_nanites", "dermal_button_nanites", "refractive_nanites", "cryo_nanites", "conductive_nanites", "shock_nanites", "emp_nanites", "temperature_nanites")
design_ids = list("hardening_nanites", "dermal_button_nanites", "refractive_nanites", "cryo_nanites", "conductive_nanites", "shock_nanites", "emp_nanites", "temperature_nanites", "antiviral_nanites", "hostile_lockdown")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/nanite_bio