Files
Yogstation/code/modules/mob/living/carbon/human/species.dm
Grandmother of 50 872817b70b Mood Code Improvements [Merge-Ready?] (#39075)
fix: having higher sanity is no longer punished by making you enter crit faster
balance: you can have 100 mood instead of 99 before it starts slowly decreasing


Remember higher sanity is BETTER

You want crit_modifier (now crit_threshold) to be LOW so you can stay in fights more (you go into crit at 5hp vs 0hp).

Why: Proc overhead, puts the load on mood, proper abstraction of mood component, values > defines, etc.
2018-07-20 00:32:44 +01:00

1677 lines
61 KiB
Plaintext

// This code handles different species in the game.
GLOBAL_LIST_EMPTY(roundstart_races)
/datum/species
var/id // if the game needs to manually check your race to do something not included in a proc here, it will use this
var/limbs_id //this is used if you want to use a different species limb sprites. Mainly used for angels as they look like humans.
var/name // this is the fluff name. these will be left generic (such as 'Lizardperson' for the lizard race) so servers can change them to whatever
var/default_color = "#FFF" // if alien colors are disabled, this is the color that will be used by that race
var/sexes = 1 // whether or not the race has sexual characteristics. at the moment this is only 0 for skeletons and shadows
var/list/offset_features = list(OFFSET_UNIFORM = list(0,0), OFFSET_ID = list(0,0), OFFSET_GLOVES = list(0,0), OFFSET_GLASSES = list(0,0), OFFSET_EARS = list(0,0), OFFSET_SHOES = list(0,0), OFFSET_S_STORE = list(0,0), OFFSET_FACEMASK = list(0,0), OFFSET_HEAD = list(0,0), OFFSET_FACE = list(0,0), OFFSET_BELT = list(0,0), OFFSET_BACK = list(0,0), OFFSET_SUIT = list(0,0), OFFSET_NECK = list(0,0))
var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color
var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent.
var/use_skintones = 0 // does it use skintones or not? (spoiler alert this is only used by humans)
var/exotic_blood = "" // If your race wants to bleed something other than bog standard blood, change this to reagent id.
var/exotic_bloodtype = "" //If your race uses a non standard bloodtype (A+, O-, AB-, etc)
var/meat = /obj/item/reagent_containers/food/snacks/meat/slab/human //What the species drops on gibbing
var/skinned_type
var/liked_food = NONE
var/disliked_food = GROSS
var/toxic_food = TOXIC
var/list/no_equip = list() // slots the race can't equip stuff to
var/nojumpsuit = 0 // this is sorta... weird. it basically lets you equip stuff that usually needs jumpsuits without one, like belts and pockets and ids
var/blacklisted = 0 //Flag to exclude from green slime core species.
var/dangerous_existence //A flag for transformation spells that tells them "hey if you turn a person into one of these without preperation, they'll probably die!"
var/say_mod = "says" // affects the speech message
var/list/default_features = list() // Default mutant bodyparts for this species. Don't forget to set one for every mutant bodypart you allow this species to have.
var/list/mutant_bodyparts = list() // Visible CURRENT bodyparts that are unique to a species. DO NOT USE THIS AS A LIST OF ALL POSSIBLE BODYPARTS AS IT WILL FUCK SHIT UP! Changes to this list for non-species specific bodyparts (ie cat ears and tails) should be assigned at organ level if possible. Layer hiding is handled by handle_mutant_bodyparts() below.
var/list/mutant_organs = list() //Internal organs that are unique to this race.
var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster
var/armor = 0 // overall defense for the race... or less defense, if it's negative.
var/brutemod = 1 // multiplier for brute damage
var/burnmod = 1 // multiplier for burn damage
var/coldmod = 1 // multiplier for cold damage
var/heatmod = 1 // multiplier for heat damage
var/acidmod = 1 // multiplier for acid damage // yogs - Old Plant People
var/stunmod = 1 // multiplier for stun duration
var/punchdamagelow = 0 //lowest possible punch damage
var/punchdamagehigh = 9 //highest possible punch damage
var/punchstunthreshold = 9//damage at which punches from this race will stun //yes it should be to the attacked race but it's not useful that way even if it's logical
var/siemens_coeff = 1 //base electrocution coefficient
var/damage_overlay_type = "human" //what kind of damage overlays (if any) appear on our species when wounded?
var/fixed_mut_color = "" //to use MUTCOLOR with a fixed color that's independent of dna.feature["mcolor"]
// species-only traits. Can be found in DNA.dm
var/list/species_traits = list()
// generic traits tied to having the species
var/list/inherent_traits = list()
var/list/inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
var/attack_verb = "punch" // punch-specific attack verb
var/sound/attack_sound = 'sound/weapons/punch1.ogg'
var/sound/miss_sound = 'sound/weapons/punchmiss.ogg'
var/mob/living/list/ignored_by = list() // list of mobs that will ignore this species
//Breathing!
var/obj/item/organ/lungs/mutantlungs = null
var/breathid = "o2"
var/obj/item/organ/brain/mutant_brain = /obj/item/organ/brain
var/obj/item/organ/heart/mutant_heart = /obj/item/organ/heart
var/obj/item/organ/eyes/mutanteyes = /obj/item/organ/eyes
var/obj/item/organ/ears/mutantears = /obj/item/organ/ears
var/obj/item/mutanthands
var/obj/item/organ/tongue/mutanttongue = /obj/item/organ/tongue
var/obj/item/organ/tail/mutanttail = null
var/obj/item/organ/liver/mutantliver
var/obj/item/organ/stomach/mutantstomach
var/override_float = FALSE
///////////
// PROCS //
///////////
/datum/species/New()
if(!limbs_id) //if we havent set a limbs id to use, just use our own id
limbs_id = id
..()
/proc/generate_selectable_species()
for(var/I in subtypesof(/datum/species))
var/datum/species/S = new I
if(S.check_roundstart_eligible())
GLOB.roundstart_races += S.id
qdel(S)
if(!GLOB.roundstart_races.len)
GLOB.roundstart_races += "human"
/datum/species/proc/check_roundstart_eligible()
if(id in (CONFIG_GET(keyed_flag_list/roundstart_races)))
return TRUE
return FALSE
/datum/species/proc/random_name(gender,unique,lastname)
if(unique)
return random_unique_name(gender)
var/randname
if(gender == MALE)
randname = pick(GLOB.first_names_male)
else
randname = pick(GLOB.first_names_female)
if(lastname)
randname += " [lastname]"
else
randname += " [pick(GLOB.last_names)]"
return randname
//Called when cloning, copies some vars that should be kept
/datum/species/proc/copy_properties_from(datum/species/old_species)
return
//Please override this locally if you want to define when what species qualifies for what rank if human authority is enforced.
/datum/species/proc/qualifies_for_rank(rank, list/features)
if(rank in GLOB.command_positions)
return 0
return 1
//Will regenerate missing organs
/datum/species/proc/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE)
var/obj/item/organ/brain/brain = C.getorganslot(ORGAN_SLOT_BRAIN)
var/obj/item/organ/heart/heart = C.getorganslot(ORGAN_SLOT_HEART)
var/obj/item/organ/lungs/lungs = C.getorganslot(ORGAN_SLOT_LUNGS)
var/obj/item/organ/appendix/appendix = C.getorganslot(ORGAN_SLOT_APPENDIX)
var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES)
var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS)
var/obj/item/organ/tongue/tongue = C.getorganslot(ORGAN_SLOT_TONGUE)
var/obj/item/organ/liver/liver = C.getorganslot(ORGAN_SLOT_LIVER)
var/obj/item/organ/stomach/stomach = C.getorganslot(ORGAN_SLOT_STOMACH)
var/obj/item/organ/tail/tail = C.getorganslot(ORGAN_SLOT_TAIL)
var/should_have_brain = TRUE
var/should_have_heart = !(NOBLOOD in species_traits)
var/should_have_lungs = !(TRAIT_NOBREATH in inherent_traits)
var/should_have_appendix = !(TRAIT_NOHUNGER in inherent_traits)
var/should_have_eyes = TRUE
var/should_have_ears = TRUE
var/should_have_tongue = TRUE
var/should_have_liver = !(NOLIVER in species_traits)
var/should_have_stomach = !(NOSTOMACH in species_traits)
var/should_have_tail = mutanttail
if(brain && (replace_current || !should_have_brain))
if(!brain.decoy_override)//Just keep it if it's fake
brain.Remove(C,TRUE,TRUE)
QDEL_NULL(brain)
if(should_have_brain && !brain)
brain = new mutant_brain()
brain.Insert(C, TRUE, TRUE)
if(heart && (!should_have_heart || replace_current))
heart.Remove(C,1)
QDEL_NULL(heart)
if(should_have_heart && !heart)
heart = new mutant_heart()
heart.Insert(C)
if(lungs && (!should_have_lungs || replace_current))
lungs.Remove(C,1)
QDEL_NULL(lungs)
if(should_have_lungs && !lungs)
if(mutantlungs)
lungs = new mutantlungs()
else
lungs = new()
lungs.Insert(C)
if(liver && (!should_have_liver || replace_current))
liver.Remove(C,1)
QDEL_NULL(liver)
if(should_have_liver && !liver)
if(mutantliver)
liver = new mutantliver()
else
liver = new()
liver.Insert(C)
if(stomach && (!should_have_stomach || replace_current))
stomach.Remove(C,1)
QDEL_NULL(stomach)
if(should_have_stomach && !stomach)
if(mutantstomach)
stomach = new mutantstomach()
else
stomach = new()
stomach.Insert(C)
if(appendix && (!should_have_appendix || replace_current))
appendix.Remove(C,1)
QDEL_NULL(appendix)
if(should_have_appendix && !appendix)
appendix = new()
appendix.Insert(C)
if(tail && (!should_have_tail || replace_current))
tail.Remove(C,1)
QDEL_NULL(tail)
if(should_have_tail && !tail)
tail = new mutanttail()
tail.Insert(C)
if(C.get_bodypart(BODY_ZONE_HEAD))
if(eyes && (replace_current || !should_have_eyes))
eyes.Remove(C,1)
QDEL_NULL(eyes)
if(should_have_eyes && !eyes)
eyes = new mutanteyes
eyes.Insert(C)
if(ears && (replace_current || !should_have_ears))
ears.Remove(C,1)
QDEL_NULL(ears)
if(should_have_ears && !ears)
ears = new mutantears
ears.Insert(C)
if(tongue && (replace_current || !should_have_tongue))
tongue.Remove(C,1)
QDEL_NULL(tongue)
if(should_have_tongue && !tongue)
tongue = new mutanttongue
tongue.Insert(C)
if(old_species)
for(var/mutantorgan in old_species.mutant_organs)
var/obj/item/organ/I = C.getorgan(mutantorgan)
if(I)
I.Remove(C)
QDEL_NULL(I)
for(var/path in mutant_organs)
var/obj/item/organ/I = new path()
I.Insert(C)
/datum/species/proc/on_species_gain(mob/living/carbon/C, datum/species/old_species)
// Drop the items the new species can't wear
for(var/slot_id in no_equip)
var/obj/item/thing = C.get_item_by_slot(slot_id)
if(thing && (!thing.species_exception || !is_type_in_list(src,thing.species_exception)))
C.dropItemToGround(thing)
if(C.hud_used)
C.hud_used.update_locked_slots()
// this needs to be FIRST because qdel calls update_body which checks if we have DIGITIGRADE legs or not and if not then removes DIGITIGRADE from species_traits
if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Digitigrade Legs")
species_traits += DIGITIGRADE
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(FALSE)
C.mob_biotypes = inherent_biotypes
regenerate_organs(C,old_species)
if(exotic_bloodtype && C.dna.blood_type != exotic_bloodtype)
C.dna.blood_type = exotic_bloodtype
if(old_species.mutanthands)
for(var/obj/item/I in C.held_items)
if(istype(I, old_species.mutanthands))
qdel(I)
if(mutanthands)
// Drop items in hands
// If you're lucky enough to have a NODROP_1 item, then it stays.
for(var/V in C.held_items)
var/obj/item/I = V
if(istype(I))
C.dropItemToGround(I)
else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand
C.put_in_hands(new mutanthands())
for(var/X in inherent_traits)
C.add_trait(X, SPECIES_TRAIT)
if(TRAIT_VIRUSIMMUNE in inherent_traits)
for(var/datum/disease/A in C.diseases)
A.cure(FALSE)
/datum/species/proc/on_species_loss(mob/living/carbon/C)
if(C.dna.species.exotic_bloodtype)
C.dna.blood_type = random_blood_type()
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(TRUE)
for(var/X in inherent_traits)
C.remove_trait(X, SPECIES_TRAIT)
/datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour)
H.remove_overlay(HAIR_LAYER)
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
if(!HD) //Decapitated
return
if(H.has_trait(TRAIT_HUSK))
return
var/datum/sprite_accessory/S
var/list/standing = list()
var/hair_hidden = FALSE //ignored if the matching dynamic_X_suffix is non-empty
var/facialhair_hidden = FALSE // ^
var/dynamic_hair_suffix = "" //if this is non-null, and hair+suffix matches an iconstate, then we render that hair instead
var/dynamic_fhair_suffix = ""
//for augmented heads
if(HD.status == BODYPART_ROBOTIC)
return
//we check if our hat or helmet hides our facial hair.
if(H.head)
var/obj/item/I = H.head
if(istype(I, /obj/item/clothing))
var/obj/item/clothing/C = I
dynamic_fhair_suffix = C.dynamic_fhair_suffix
if(I.flags_inv & HIDEFACIALHAIR)
facialhair_hidden = TRUE
if(H.wear_mask)
var/obj/item/clothing/mask/M = H.wear_mask
dynamic_fhair_suffix = M.dynamic_fhair_suffix //mask > head in terms of facial hair
if(M.flags_inv & HIDEFACIALHAIR)
facialhair_hidden = TRUE
if(H.facial_hair_style && (FACEHAIR in species_traits) && (!facialhair_hidden || dynamic_fhair_suffix))
S = GLOB.facial_hair_styles_list[H.facial_hair_style]
if(S)
//List of all valid dynamic_fhair_suffixes
var/static/list/fextensions
if(!fextensions)
var/icon/fhair_extensions = icon('icons/mob/facialhair_extensions.dmi')
fextensions = list()
for(var/s in fhair_extensions.IconStates(1))
fextensions[s] = TRUE
qdel(fhair_extensions)
//Is hair+dynamic_fhair_suffix a valid iconstate?
var/fhair_state = S.icon_state
var/fhair_file = S.icon
if(fextensions[fhair_state+dynamic_fhair_suffix])
fhair_state += dynamic_fhair_suffix
fhair_file = 'icons/mob/facialhair_extensions.dmi'
var/mutable_appearance/facial_overlay = mutable_appearance(fhair_file, fhair_state, -HAIR_LAYER)
if(!forced_colour)
if(hair_color)
if(hair_color == "mutcolor")
facial_overlay.color = "#" + H.dna.features["mcolor"]
else
facial_overlay.color = "#" + hair_color
else
facial_overlay.color = "#" + H.facial_hair_color
else
facial_overlay.color = forced_colour
facial_overlay.alpha = hair_alpha
standing += facial_overlay
if(H.head)
var/obj/item/I = H.head
if(istype(I, /obj/item/clothing))
var/obj/item/clothing/C = I
dynamic_hair_suffix = C.dynamic_hair_suffix
if(I.flags_inv & HIDEHAIR)
hair_hidden = TRUE
if(H.wear_mask)
var/obj/item/clothing/mask/M = H.wear_mask
if(!dynamic_hair_suffix) //head > mask in terms of head hair
dynamic_hair_suffix = M.dynamic_hair_suffix
if(M.flags_inv & HIDEHAIR)
hair_hidden = TRUE
if(!hair_hidden || dynamic_hair_suffix)
var/mutable_appearance/hair_overlay = mutable_appearance(layer = -HAIR_LAYER)
if(!hair_hidden && !H.getorgan(/obj/item/organ/brain)) //Applies the debrained overlay if there is no brain
if(!(NOBLOOD in species_traits))
hair_overlay.icon = 'icons/mob/human_face.dmi'
hair_overlay.icon_state = "debrained"
else if(H.hair_style && (HAIR in species_traits))
S = GLOB.hair_styles_list[H.hair_style]
if(S)
//List of all valid dynamic_hair_suffixes
var/static/list/extensions
if(!extensions)
var/icon/hair_extensions = icon('icons/mob/hair_extensions.dmi') //hehe
extensions = list()
for(var/s in hair_extensions.IconStates(1))
extensions[s] = TRUE
qdel(hair_extensions)
//Is hair+dynamic_hair_suffix a valid iconstate?
var/hair_state = S.icon_state
var/hair_file = S.icon
if(extensions[hair_state+dynamic_hair_suffix])
hair_state += dynamic_hair_suffix
hair_file = 'icons/mob/hair_extensions.dmi'
hair_overlay.icon = hair_file
hair_overlay.icon_state = hair_state
if(!forced_colour)
if(hair_color)
if(hair_color == "mutcolor")
hair_overlay.color = "#" + H.dna.features["mcolor"]
else
hair_overlay.color = "#" + hair_color
else
hair_overlay.color = "#" + H.hair_color
else
hair_overlay.color = forced_colour
hair_overlay.alpha = hair_alpha
if(OFFSET_FACE in H.dna.species.offset_features)
hair_overlay.pixel_x += H.dna.species.offset_features[OFFSET_FACE][1]
hair_overlay.pixel_y += H.dna.species.offset_features[OFFSET_FACE][2]
if(hair_overlay.icon)
standing += hair_overlay
if(standing.len)
H.overlays_standing[HAIR_LAYER] = standing
H.apply_overlay(HAIR_LAYER)
/datum/species/proc/handle_body(mob/living/carbon/human/H)
H.remove_overlay(BODY_LAYER)
var/list/standing = list()
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
if(HD && !(H.has_trait(TRAIT_HUSK)))
// lipstick
if(H.lip_style && (LIPS in species_traits))
var/mutable_appearance/lip_overlay = mutable_appearance('icons/mob/human_face.dmi', "lips_[H.lip_style]", -BODY_LAYER)
lip_overlay.color = H.lip_color
if(OFFSET_FACE in H.dna.species.offset_features)
lip_overlay.pixel_x += H.dna.species.offset_features[OFFSET_FACE][1]
lip_overlay.pixel_y += H.dna.species.offset_features[OFFSET_FACE][2]
standing += lip_overlay
// eyes
if(!(NOEYES in species_traits))
var/has_eyes = H.getorganslot(ORGAN_SLOT_EYES)
var/mutable_appearance/eye_overlay
if(!has_eyes)
eye_overlay = mutable_appearance('icons/mob/human_face.dmi', "eyes_missing", -BODY_LAYER)
else
eye_overlay = mutable_appearance('icons/mob/human_face.dmi', "eyes", -BODY_LAYER)
if((EYECOLOR in species_traits) && has_eyes)
eye_overlay.color = "#" + H.eye_color
if(OFFSET_FACE in H.dna.species.offset_features)
eye_overlay.pixel_x += H.dna.species.offset_features[OFFSET_FACE][1]
eye_overlay.pixel_y += H.dna.species.offset_features[OFFSET_FACE][2]
standing += eye_overlay
//Underwear, Undershirts & Socks
if(!(NO_UNDERWEAR in species_traits))
if(H.underwear)
var/datum/sprite_accessory/underwear/underwear = GLOB.underwear_list[H.underwear]
if(underwear)
standing += mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
if(H.undershirt)
var/datum/sprite_accessory/undershirt/undershirt = GLOB.undershirt_list[H.undershirt]
if(undershirt)
if(H.dna.species.sexes && H.gender == FEMALE)
standing += wear_female_version(undershirt.icon_state, undershirt.icon, BODY_LAYER)
else
standing += mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER)
if(H.socks && H.get_num_legs(FALSE) >= 2 && !(DIGITIGRADE in species_traits))
var/datum/sprite_accessory/socks/socks = GLOB.socks_list[H.socks]
if(socks)
standing += mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER)
if(standing.len)
H.overlays_standing[BODY_LAYER] = standing
H.apply_overlay(BODY_LAYER)
handle_mutant_bodyparts(H)
/datum/species/proc/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour)
var/list/bodyparts_to_add = mutant_bodyparts.Copy()
var/list/relevent_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
var/list/standing = list()
H.remove_overlay(BODY_BEHIND_LAYER)
H.remove_overlay(BODY_ADJ_LAYER)
H.remove_overlay(BODY_FRONT_LAYER)
if(!mutant_bodyparts)
return
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
if("tail_lizard" in mutant_bodyparts)
if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
bodyparts_to_add -= "tail_lizard"
if("waggingtail_lizard" in mutant_bodyparts)
if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
bodyparts_to_add -= "waggingtail_lizard"
else if ("tail_lizard" in mutant_bodyparts)
bodyparts_to_add -= "waggingtail_lizard"
if("tail_human" in mutant_bodyparts)
if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
bodyparts_to_add -= "tail_human"
if("waggingtail_human" in mutant_bodyparts)
if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
bodyparts_to_add -= "waggingtail_human"
else if ("tail_human" in mutant_bodyparts)
bodyparts_to_add -= "waggingtail_human"
if("spines" in mutant_bodyparts)
if(!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
bodyparts_to_add -= "spines"
if("waggingspines" in mutant_bodyparts)
if(!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
bodyparts_to_add -= "waggingspines"
else if ("tail" in mutant_bodyparts)
bodyparts_to_add -= "waggingspines"
if("snout" in mutant_bodyparts) //Take a closer look at that snout!
if((H.wear_mask && (H.wear_mask.flags_inv & HIDEFACE)) || (H.head && (H.head.flags_inv & HIDEFACE)) || !HD || HD.status == BODYPART_ROBOTIC)
bodyparts_to_add -= "snout"
if("frills" in mutant_bodyparts)
if(!H.dna.features["frills"] || H.dna.features["frills"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || !HD || HD.status == BODYPART_ROBOTIC)
bodyparts_to_add -= "frills"
if("horns" in mutant_bodyparts)
if(!H.dna.features["horns"] || H.dna.features["horns"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
bodyparts_to_add -= "horns"
if("ears" in mutant_bodyparts)
if(!H.dna.features["ears"] || H.dna.features["ears"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
bodyparts_to_add -= "ears"
if("wings" in mutant_bodyparts)
if(!H.dna.features["wings"] || H.dna.features["wings"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception))))
bodyparts_to_add -= "wings"
if("wings_open" in mutant_bodyparts)
if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception)))
bodyparts_to_add -= "wings_open"
else if ("wings" in mutant_bodyparts)
bodyparts_to_add -= "wings_open"
//Digitigrade legs are stuck in the phantom zone between true limbs and mutant bodyparts. Mainly it just needs more agressive updating than most limbs.
var/update_needed = FALSE
var/not_digitigrade = TRUE
for(var/X in H.bodyparts)
var/obj/item/bodypart/O = X
if(!O.use_digitigrade)
continue
not_digitigrade = FALSE
if(!(DIGITIGRADE in species_traits)) //Someone cut off a digitigrade leg and tacked it on
species_traits += DIGITIGRADE
var/should_be_squished = FALSE
if(H.wear_suit && ((H.wear_suit.flags_inv & HIDEJUMPSUIT) || (H.wear_suit.body_parts_covered & LEGS)) || (H.w_uniform && (H.w_uniform.body_parts_covered & LEGS)))
should_be_squished = TRUE
if(O.use_digitigrade == FULL_DIGITIGRADE && should_be_squished)
O.use_digitigrade = SQUISHED_DIGITIGRADE
update_needed = TRUE
else if(O.use_digitigrade == SQUISHED_DIGITIGRADE && !should_be_squished)
O.use_digitigrade = FULL_DIGITIGRADE
update_needed = TRUE
if(update_needed)
H.update_body_parts()
if(not_digitigrade && (DIGITIGRADE in species_traits)) //Curse is lifted
species_traits -= DIGITIGRADE
if(!bodyparts_to_add)
return
var/g = (H.gender == FEMALE) ? "f" : "m"
for(var/layer in relevent_layers)
var/layertext = mutant_bodyparts_layertext(layer)
for(var/bodypart in bodyparts_to_add)
var/datum/sprite_accessory/S
switch(bodypart)
if("tail_lizard")
S = GLOB.tails_list_lizard[H.dna.features["tail_lizard"]]
if("waggingtail_lizard")
S.= GLOB.animated_tails_list_lizard[H.dna.features["tail_lizard"]]
if("tail_human")
S = GLOB.tails_list_human[H.dna.features["tail_human"]]
if("waggingtail_human")
S.= GLOB.animated_tails_list_human[H.dna.features["tail_human"]]
if("spines")
S = GLOB.spines_list[H.dna.features["spines"]]
if("waggingspines")
S.= GLOB.animated_spines_list[H.dna.features["spines"]]
if("snout")
S = GLOB.snouts_list[H.dna.features["snout"]]
if("frills")
S = GLOB.frills_list[H.dna.features["frills"]]
if("horns")
S = GLOB.horns_list[H.dna.features["horns"]]
if("ears")
S = GLOB.ears_list[H.dna.features["ears"]]
if("body_markings")
S = GLOB.body_markings_list[H.dna.features["body_markings"]]
if("wings")
S = GLOB.wings_list[H.dna.features["wings"]]
if("wingsopen")
S = GLOB.wings_open_list[H.dna.features["wings"]]
if("legs")
S = GLOB.legs_list[H.dna.features["legs"]]
if("moth_wings")
S = GLOB.moth_wings_list[H.dna.features["moth_wings"]]
if("caps")
S = GLOB.caps_list[H.dna.features["caps"]]
if(!S || S.icon_state == "none")
continue
var/mutable_appearance/accessory_overlay = mutable_appearance(S.icon, layer = -layer)
//A little rename so we don't have to use tail_lizard or tail_human when naming the sprites.
if(bodypart == "tail_lizard" || bodypart == "tail_human")
bodypart = "tail"
else if(bodypart == "waggingtail_lizard" || bodypart == "waggingtail_human")
bodypart = "waggingtail"
if(S.gender_specific)
accessory_overlay.icon_state = "[g]_[bodypart]_[S.icon_state]_[layertext]"
else
accessory_overlay.icon_state = "m_[bodypart]_[S.icon_state]_[layertext]"
if(S.center)
accessory_overlay = center_image(accessory_overlay, S.dimension_x, S.dimension_y)
if(!(H.has_trait(TRAIT_HUSK)))
if(!forced_colour)
switch(S.color_src)
if(MUTCOLORS)
if(fixed_mut_color)
accessory_overlay.color = "#[fixed_mut_color]"
else
accessory_overlay.color = "#[H.dna.features["mcolor"]]"
if(HAIR)
if(hair_color == "mutcolor")
accessory_overlay.color = "#[H.dna.features["mcolor"]]"
else
accessory_overlay.color = "#[H.hair_color]"
if(FACEHAIR)
accessory_overlay.color = "#[H.facial_hair_color]"
if(EYECOLOR)
accessory_overlay.color = "#[H.eye_color]"
else
accessory_overlay.color = forced_colour
standing += accessory_overlay
if(S.hasinner)
var/mutable_appearance/inner_accessory_overlay = mutable_appearance(S.icon, layer = -layer)
if(S.gender_specific)
inner_accessory_overlay.icon_state = "[g]_[bodypart]inner_[S.icon_state]_[layertext]"
else
inner_accessory_overlay.icon_state = "m_[bodypart]inner_[S.icon_state]_[layertext]"
if(S.center)
inner_accessory_overlay = center_image(inner_accessory_overlay, S.dimension_x, S.dimension_y)
standing += inner_accessory_overlay
H.overlays_standing[layer] = standing.Copy()
standing = list()
H.apply_overlay(BODY_BEHIND_LAYER)
H.apply_overlay(BODY_ADJ_LAYER)
H.apply_overlay(BODY_FRONT_LAYER)
//This exists so sprite accessories can still be per-layer without having to include that layer's
//number in their sprite name, which causes issues when those numbers change.
/datum/species/proc/mutant_bodyparts_layertext(layer)
switch(layer)
if(BODY_BEHIND_LAYER)
return "BEHIND"
if(BODY_ADJ_LAYER)
return "ADJ"
if(BODY_FRONT_LAYER)
return "FRONT"
/datum/species/proc/spec_life(mob/living/carbon/human/H)
if(H.has_trait(TRAIT_NOBREATH))
H.setOxyLoss(0)
H.losebreath = 0
var/takes_crit_damage = (!H.has_trait(TRAIT_NOCRITDAMAGE))
if((H.health < H.crit_threshold) && takes_crit_damage)
H.adjustBruteLoss(1)
/datum/species/proc/spec_death(gibbed, mob/living/carbon/human/H)
return
/datum/species/proc/auto_equip(mob/living/carbon/human/H)
// handles the equipping of species-specific gear
return
/datum/species/proc/can_equip(obj/item/I, slot, disable_warning, mob/living/carbon/human/H, bypass_equip_delay_self = FALSE)
if(slot in no_equip)
if(!I.species_exception || !is_type_in_list(src, I.species_exception))
return FALSE
var/num_arms = H.get_num_arms(FALSE)
var/num_legs = H.get_num_legs(FALSE)
switch(slot)
if(SLOT_HANDS)
if(H.get_empty_held_indexes())
return TRUE
return FALSE
if(SLOT_WEAR_MASK)
if(H.wear_mask)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_MASK))
return FALSE
if(!H.get_bodypart(BODY_ZONE_HEAD))
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_NECK)
if(H.wear_neck)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_NECK) )
return FALSE
return TRUE
if(SLOT_BACK)
if(H.back)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_BACK) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_WEAR_SUIT)
if(H.wear_suit)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_OCLOTHING) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_GLOVES)
if(H.gloves)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_GLOVES) )
return FALSE
if(num_arms < 2)
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_SHOES)
if(H.shoes)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_FEET) )
return FALSE
if(num_legs < 2)
return FALSE
if(DIGITIGRADE in species_traits)
if(!disable_warning)
to_chat(H, "<span class='warning'>The footwear around here isn't compatible with your feet!</span>")
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_BELT)
if(H.belt)
return FALSE
var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_CHEST)
if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
if(!disable_warning)
to_chat(H, "<span class='warning'>You need a jumpsuit before you can attach this [I.name]!</span>")
return FALSE
if(!(I.slot_flags & ITEM_SLOT_BELT))
return
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_GLASSES)
if(H.glasses)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_EYES))
return FALSE
if(!H.get_bodypart(BODY_ZONE_HEAD))
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_HEAD)
if(H.head)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_HEAD))
return FALSE
if(!H.get_bodypart(BODY_ZONE_HEAD))
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_EARS)
if(H.ears)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_EARS))
return FALSE
if(!H.get_bodypart(BODY_ZONE_HEAD))
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_W_UNIFORM)
if(H.w_uniform)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_ICLOTHING) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_WEAR_ID)
if(H.wear_id)
return FALSE
var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_CHEST)
if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
if(!disable_warning)
to_chat(H, "<span class='warning'>You need a jumpsuit before you can attach this [I.name]!</span>")
return FALSE
if( !(I.slot_flags & ITEM_SLOT_ID) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_L_STORE)
if(I.item_flags & NODROP) //Pockets aren't visible, so you can't move NODROP_1 items into them.
return FALSE
if(H.l_store)
return FALSE
var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_L_LEG)
if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
if(!disable_warning)
to_chat(H, "<span class='warning'>You need a jumpsuit before you can attach this [I.name]!</span>")
return FALSE
if(I.slot_flags & ITEM_SLOT_DENYPOCKET)
return FALSE
if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_POCKET) )
return TRUE
if(SLOT_R_STORE)
if(I.item_flags & NODROP)
return FALSE
if(H.r_store)
return FALSE
var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_R_LEG)
if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
if(!disable_warning)
to_chat(H, "<span class='warning'>You need a jumpsuit before you can attach this [I.name]!</span>")
return FALSE
if(I.slot_flags & ITEM_SLOT_DENYPOCKET)
return FALSE
if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_POCKET) )
return TRUE
return FALSE
if(SLOT_S_STORE)
if(I.item_flags & NODROP)
return FALSE
if(H.s_store)
return FALSE
if(!H.wear_suit)
if(!disable_warning)
to_chat(H, "<span class='warning'>You need a suit before you can attach this [I.name]!</span>")
return FALSE
if(!H.wear_suit.allowed)
if(!disable_warning)
to_chat(H, "You somehow have a suit with no defined allowed items for suit storage, stop that.")
return FALSE
if(I.w_class > WEIGHT_CLASS_BULKY)
if(!disable_warning)
to_chat(H, "The [I.name] is too big to attach.") //should be src?
return FALSE
if( istype(I, /obj/item/pda) || istype(I, /obj/item/pen) || is_type_in_list(I, H.wear_suit.allowed) )
return TRUE
return FALSE
if(SLOT_HANDCUFFED)
if(H.handcuffed)
return FALSE
if(!istype(I, /obj/item/restraints/handcuffs))
return FALSE
if(num_arms < 2)
return FALSE
return TRUE
if(SLOT_LEGCUFFED)
if(H.legcuffed)
return FALSE
if(!istype(I, /obj/item/restraints/legcuffs))
return FALSE
if(num_legs < 2)
return FALSE
return TRUE
if(SLOT_IN_BACKPACK)
if(H.back)
if(SEND_SIGNAL(H.back, COMSIG_TRY_STORAGE_CAN_INSERT, I, H, TRUE))
return TRUE
return FALSE
return FALSE //Unsupported slot
/datum/species/proc/equip_delay_self_check(obj/item/I, mob/living/carbon/human/H, bypass_equip_delay_self)
if(!I.equip_delay_self || bypass_equip_delay_self)
return TRUE
H.visible_message("<span class='notice'>[H] start putting on [I]...</span>", "<span class='notice'>You start putting on [I]...</span>")
return do_after(H, I.equip_delay_self, target = H)
/datum/species/proc/before_equip_job(datum/job/J, mob/living/carbon/human/H)
return
/datum/species/proc/after_equip_job(datum/job/J, mob/living/carbon/human/H)
H.update_mutant_bodyparts()
/datum/species/proc/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(chem.id == exotic_blood)
H.blood_volume = min(H.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM)
H.reagents.del_reagent(chem.id)
return 1
return FALSE
/datum/species/proc/handle_speech(message, mob/living/carbon/human/H)
return message
//return a list of spans or an empty list
/datum/species/proc/get_spans()
return list()
/datum/species/proc/check_weakness(obj/item, mob/living/attacker)
return FALSE
////////
//LIFE//
////////
/datum/species/proc/handle_digestion(mob/living/carbon/human/H)
//The fucking TRAIT_FAT mutation is the dumbest shit ever. It makes the code so difficult to work with
if(H.has_trait(TRAIT_FAT))//I share your pain, past coder.
if(H.overeatduration < 100)
to_chat(H, "<span class='notice'>You feel fit again!</span>")
H.remove_trait(TRAIT_FAT, OBESITY)
H.update_inv_w_uniform()
H.update_inv_wear_suit()
else
if(H.overeatduration >= 100)
to_chat(H, "<span class='danger'>You suddenly feel blubbery!</span>")
H.add_trait(TRAIT_FAT, OBESITY)
H.update_inv_w_uniform()
H.update_inv_wear_suit()
// nutrition decrease and satiety
if (H.nutrition > 0 && H.stat != DEAD && !H.has_trait(TRAIT_NOHUNGER))
// THEY HUNGER
var/hunger_rate = HUNGER_FACTOR
GET_COMPONENT_FROM(mood, /datum/component/mood, H)
if(mood && mood.sanity > SANITY_DISTURBED)
hunger_rate *= max(0.5, 1 - 0.002 * mood.sanity) //0.85 to 0.75
if(H.satiety > 0)
H.satiety--
if(H.satiety < 0)
H.satiety++
if(prob(round(-H.satiety/40)))
H.Jitter(5)
hunger_rate = 3 * HUNGER_FACTOR
hunger_rate *= H.physiology.hunger_mod
H.nutrition = max(0, H.nutrition - hunger_rate)
if (H.nutrition > NUTRITION_LEVEL_FULL)
if(H.overeatduration < 600) //capped so people don't take forever to unfat
H.overeatduration++
else
if(H.overeatduration > 1)
H.overeatduration -= 2 //doubled the unfat rate
//metabolism change
if(H.nutrition > NUTRITION_LEVEL_FAT)
H.metabolism_efficiency = 1
else if(H.nutrition > NUTRITION_LEVEL_FED && H.satiety > 80)
if(H.metabolism_efficiency != 1.25 && !H.has_trait(TRAIT_NOHUNGER))
to_chat(H, "<span class='notice'>You feel vigorous.</span>")
H.metabolism_efficiency = 1.25
else if(H.nutrition < NUTRITION_LEVEL_STARVING + 50)
if(H.metabolism_efficiency != 0.8)
to_chat(H, "<span class='notice'>You feel sluggish.</span>")
H.metabolism_efficiency = 0.8
else
if(H.metabolism_efficiency == 1.25)
to_chat(H, "<span class='notice'>You no longer feel vigorous.</span>")
H.metabolism_efficiency = 1
switch(H.nutrition)
if(NUTRITION_LEVEL_FULL to INFINITY)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "nutrition", /datum/mood_event/nutrition/fat)
H.throw_alert("nutrition", /obj/screen/alert/fat)
if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "nutrition", /datum/mood_event/nutrition/wellfed)
H.clear_alert("nutrition")
if( NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "nutrition", /datum/mood_event/nutrition/fed)
H.clear_alert("nutrition")
if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED)
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "nutrition")
H.clear_alert("nutrition")
if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "nutrition", /datum/mood_event/nutrition/hungry)
H.throw_alert("nutrition", /obj/screen/alert/hungry)
if(0 to NUTRITION_LEVEL_STARVING)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "nutrition", /datum/mood_event/nutrition/starving)
H.throw_alert("nutrition", /obj/screen/alert/starving)
/datum/species/proc/update_health_hud(mob/living/carbon/human/H)
return 0
/datum/species/proc/handle_mutations_and_radiation(mob/living/carbon/human/H)
. = FALSE
var/radiation = H.radiation
if(H.has_trait(TRAIT_RADIMMUNE))
radiation = 0
return TRUE
if(radiation > RAD_MOB_KNOCKDOWN && prob(RAD_MOB_KNOCKDOWN_PROB))
if(!H.IsKnockdown())
H.emote("collapse")
H.Knockdown(RAD_MOB_KNOCKDOWN_AMOUNT)
to_chat(H, "<span class='danger'>You feel weak.</span>")
if(radiation > RAD_MOB_VOMIT && prob(RAD_MOB_VOMIT_PROB))
H.vomit(10, TRUE)
if(radiation > RAD_MOB_MUTATE)
if(prob(1))
to_chat(H, "<span class='danger'>You mutate!</span>")
H.randmutb()
H.emote("gasp")
H.domutcheck()
if(radiation > RAD_MOB_HAIRLOSS)
if(prob(15) && !(H.hair_style == "Bald") && (HAIR in species_traits))
to_chat(H, "<span class='danger'>Your hair starts to fall out in clumps...</span>")
addtimer(CALLBACK(src, .proc/go_bald, H), 50)
/datum/species/proc/go_bald(mob/living/carbon/human/H)
if(QDELETED(H)) //may be called from a timer
return
H.facial_hair_style = "Shaved"
H.hair_style = "Bald"
H.update_hair()
////////////////
// MOVE SPEED //
////////////////
/datum/species/proc/movement_delay(mob/living/carbon/human/H)
. = 0 //We start at 0.
var/flight = 0 //Check for flight and flying items
var/flightpack = 0
var/ignoreslow = 0
var/gravity = 0
var/obj/item/flightpack/F = H.get_flightpack()
if(istype(F) && F.flight)
flightpack = 1
if(H.movement_type & FLYING)
flight = 1
gravity = H.has_gravity()
if(!flightpack && gravity) //Check for chemicals and innate speedups and slowdowns if we're moving using our body and not a flying suit
if(H.has_trait(TRAIT_GOTTAGOFAST))
. -= 1
if(H.has_trait(TRAIT_GOTTAGOREALLYFAST))
. -= 2
. += speedmod
. += H.physiology.speed_mod
if(H.has_trait(TRAIT_IGNORESLOWDOWN))
ignoreslow = 1
if(!gravity)
var/obj/item/tank/jetpack/J = H.back
var/obj/item/clothing/suit/space/hardsuit/C = H.wear_suit
var/obj/item/organ/cyberimp/chest/thrusters/T = H.getorganslot(ORGAN_SLOT_THRUSTERS)
if(!istype(J) && istype(C))
J = C.jetpack
if(istype(J) && J.full_speed && J.allow_thrust(0.01, H)) //Prevents stacking
. -= 2
else if(istype(T) && T.allow_thrust(0.01, H))
. -= 2
if(flightpack && F.boost)
. -= F.boost_speed
else if(flightpack && F.brake)
. += 1
if(!ignoreslow && !flightpack && gravity)
if(H.wear_suit)
. += H.wear_suit.slowdown
if(H.shoes)
. += H.shoes.slowdown
if(H.back)
. += H.back.slowdown
for(var/obj/item/I in H.held_items)
if(I.item_flags & SLOWS_WHILE_IN_HAND)
. += I.slowdown
var/health_deficiency = (100 - H.health + H.staminaloss)
if(health_deficiency >= 40)
if(flight)
. += (health_deficiency / 75)
else
. += (health_deficiency / 25)
if(CONFIG_GET(flag/disable_human_mood))
var/hungry = (500 - H.nutrition) / 5 //So overeat would be 100 and default level would be 80
if((hungry >= 70) && !flight) //Being hungry will still allow you to use a flightsuit/wings.
. += hungry / 50
//Moving in high gravity is very slow (Flying too)
if(gravity > STANDARD_GRAVITY)
var/grav_force = min(gravity - STANDARD_GRAVITY,3)
. += 1 + grav_force
GET_COMPONENT_FROM(mood, /datum/component/mood, H)
if(mood && !flight) //How can depression slow you down if you can just fly away from your problems?
switch(mood.sanity)
if(SANITY_INSANE to SANITY_CRAZY)
. += 1.5
if(SANITY_CRAZY to SANITY_UNSTABLE)
. += 1
if(SANITY_UNSTABLE to SANITY_DISTURBED)
. += 0.5
if(H.has_trait(TRAIT_FAT))
. += (1.5 - flight)
if(H.bodytemperature < BODYTEMP_COLD_DAMAGE_LIMIT && !H.has_trait(TRAIT_RESISTCOLD))
. += (BODYTEMP_COLD_DAMAGE_LIMIT - H.bodytemperature) / COLD_SLOWDOWN_FACTOR
return .
//////////////////
// ATTACK PROCS //
//////////////////
//////////////////
// ATTACK PROCS //
//////////////////
/datum/species/proc/help(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(target.health >= 0 && !(target.has_trait(TRAIT_FAKEDEATH)))
target.help_shake_act(user)
if(target != user)
add_logs(user, target, "shaked")
return 1
else
var/we_breathe = !user.has_trait(TRAIT_NOBREATH)
var/we_lung = user.getorganslot(ORGAN_SLOT_LUNGS)
if(we_breathe && we_lung)
user.do_cpr(target)
else if(we_breathe && !we_lung)
to_chat(user, "<span class='warning'>You have no lungs to breathe with, so you cannot peform CPR.</span>")
else
to_chat(user, "<span class='notice'>You do not breathe, so you cannot perform CPR.</span>")
/datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(target.check_block())
target.visible_message("<span class='warning'>[target] blocks [user]'s grab attempt!</span>")
return 0
if(attacker_style && attacker_style.grab_act(user,target))
return 1
else
target.grabbedby(user)
return 1
/datum/species/proc/harm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(user.has_trait(TRAIT_PACIFISM))
to_chat(user, "<span class='warning'>You don't want to harm [target]!</span>")
return FALSE
if(target.check_block())
target.visible_message("<span class='warning'>[target] blocks [user]'s attack!</span>")
return FALSE
if(attacker_style && attacker_style.harm_act(user,target))
return TRUE
else
var/atk_verb = user.dna.species.attack_verb
if(target.lying)
atk_verb = "kick"
switch(atk_verb)
if("kick")
user.do_attack_animation(target, ATTACK_EFFECT_KICK)
if("slash")
user.do_attack_animation(target, ATTACK_EFFECT_CLAW)
if("smash")
user.do_attack_animation(target, ATTACK_EFFECT_SMASH)
else
user.do_attack_animation(target, ATTACK_EFFECT_PUNCH)
var/damage = rand(user.dna.species.punchdamagelow, user.dna.species.punchdamagehigh)
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected))
if(!damage || !affecting)
playsound(target.loc, user.dna.species.miss_sound, 25, 1, -1)
target.visible_message("<span class='danger'>[user] has attempted to [atk_verb] [target]!</span>",\
"<span class='userdanger'>[user] has attempted to [atk_verb] [target]!</span>", null, COMBAT_MESSAGE_RANGE)
return FALSE
var/armor_block = target.run_armor_check(affecting, "melee")
playsound(target.loc, user.dna.species.attack_sound, 25, 1, -1)
target.visible_message("<span class='danger'>[user] has [atk_verb]ed [target]!</span>", \
"<span class='userdanger'>[user] has [atk_verb]ed [target]!</span>", null, COMBAT_MESSAGE_RANGE)
if(user.limb_destroyer)
target.dismembering_strike(user, affecting.body_zone)
target.apply_damage(damage, BRUTE, affecting, armor_block)
add_logs(user, target, "punched")
if((target.stat != DEAD) && damage >= user.dna.species.punchstunthreshold)
target.visible_message("<span class='danger'>[user] has knocked [target] down!</span>", \
"<span class='userdanger'>[user] has knocked [target] down!</span>", null, COMBAT_MESSAGE_RANGE)
target.apply_effect(80, EFFECT_KNOCKDOWN, armor_block)
target.forcesay(GLOB.hit_appends)
else if(target.lying)
target.forcesay(GLOB.hit_appends)
/datum/species/proc/disarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(target.check_block())
target.visible_message("<span class='warning'>[target] blocks [user]'s disarm attempt!</span>")
return 0
if(attacker_style && attacker_style.disarm_act(user,target))
return 1
else
user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
if(target.w_uniform)
target.w_uniform.add_fingerprint(user)
var/randomized_zone = ran_zone(user.zone_selected)
SEND_SIGNAL(target, COMSIG_HUMAN_DISARM_HIT, user, user.zone_selected)
var/obj/item/bodypart/affecting = target.get_bodypart(randomized_zone)
var/randn = rand(1, 100)
if(randn <= 25)
playsound(target, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
target.visible_message("<span class='danger'>[user] has pushed [target]!</span>",
"<span class='userdanger'>[user] has pushed [target]!</span>", null, COMBAT_MESSAGE_RANGE)
target.apply_effect(40, EFFECT_KNOCKDOWN, target.run_armor_check(affecting, "melee", "Your armor prevents your fall!", "Your armor softens your fall!"))
target.forcesay(GLOB.hit_appends)
add_logs(user, target, "pushed over")
return
if(randn <= 60)
var/obj/item/I = null
if(target.pulling)
target.visible_message("<span class='warning'>[user] has broken [target]'s grip on [target.pulling]!</span>")
target.stop_pulling()
else
I = target.get_active_held_item()
if(target.dropItemToGround(I))
target.visible_message("<span class='danger'>[user] has disarmed [target]!</span>", \
"<span class='userdanger'>[user] has disarmed [target]!</span>", null, COMBAT_MESSAGE_RANGE)
else
I = null
playsound(target, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
add_logs(user, target, "disarmed", "[I ? " removing \the [I]" : ""]")
return
playsound(target, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
target.visible_message("<span class='danger'>[user] attempted to disarm [target]!</span>", \
"<span class='userdanger'>[user] attemped to disarm [target]!</span>", null, COMBAT_MESSAGE_RANGE)
add_logs(user, target, "attempted to disarm")
/datum/species/proc/spec_hitby(atom/movable/AM, mob/living/carbon/human/H)
return
/datum/species/proc/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style)
if(!istype(M))
return
CHECK_DNA_AND_SPECIES(M)
CHECK_DNA_AND_SPECIES(H)
if(!istype(M)) //sanity check for drones.
return
if(M.mind)
attacker_style = M.mind.martial_art
if((M != H) && M.a_intent != INTENT_HELP && H.check_shields(M, 0, M.name, attack_type = UNARMED_ATTACK))
add_logs(M, H, "attempted to touch")
H.visible_message("<span class='warning'>[M] attempted to touch [H]!</span>")
return 0
switch(M.a_intent)
if("help")
help(M, H, attacker_style)
if("grab")
grab(M, H, attacker_style)
if("harm")
harm(M, H, attacker_style)
if("disarm")
disarm(M, H, attacker_style)
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H)
// Allows you to put in item-specific reactions based on species
if(user != H)
if(H.check_shields(I, I.force, "the [I.name]", MELEE_ATTACK, I.armour_penetration))
return 0
if(H.check_block())
H.visible_message("<span class='warning'>[H] blocks [I]!</span>")
return 0
var/hit_area
if(!affecting) //Something went wrong. Maybe the limb is missing?
affecting = H.bodyparts[1]
hit_area = affecting.name
var/def_zone = affecting.body_zone
var/armor_block = H.run_armor_check(affecting, "melee", "<span class='notice'>Your armor has protected your [hit_area].</span>", "<span class='notice'>Your armor has softened a hit to your [hit_area].</span>",I.armour_penetration)
armor_block = min(90,armor_block) //cap damage reduction at 90%
var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords)
var/weakness = H.check_weakness(I, user)
apply_damage(I.force * weakness, I.damtype, def_zone, armor_block, H)
H.send_item_attack_message(I, user, hit_area)
if(!I.force)
return 0 //item force is zero
//dismemberment
var/probability = I.get_dismemberment_chance(affecting)
if(prob(probability) || (H.has_trait(TRAIT_EASYDISMEMBER) && prob(probability))) //try twice
if(affecting.dismember(I.damtype))
I.add_mob_blood(H)
playsound(get_turf(H), I.get_dismember_sound(), 80, 1)
var/bloody = 0
if(((I.damtype == BRUTE) && I.force && prob(25 + (I.force * 2))))
if(affecting.status == BODYPART_ORGANIC)
I.add_mob_blood(H) //Make the weapon bloody, not the person.
if(prob(I.force * 2)) //blood spatter!
bloody = 1
var/turf/location = H.loc
if(istype(location))
H.add_splatter_floor(location)
if(get_dist(user, H) <= 1) //people with TK won't get smeared with blood
user.add_mob_blood(H)
switch(hit_area)
if(BODY_ZONE_HEAD)
if(H.stat == CONSCIOUS && armor_block < 50)
if(prob(I.force))
H.visible_message("<span class='danger'>[H] has been knocked senseless!</span>", \
"<span class='userdanger'>[H] has been knocked senseless!</span>")
H.confused = max(H.confused, 20)
H.adjustBrainLoss(20)
H.adjust_blurriness(10)
if(prob(10))
H.gain_trauma(/datum/brain_trauma/mild/concussion)
else
if(!I.is_sharp())
H.adjustBrainLoss(I.force * 0.2)
if(!I.is_sharp() && prob(I.force + ((100 - H.health) * 0.5)) && H != user) // rev deconversion through blunt trauma.
var/datum/antagonist/rev/rev = H.mind.has_antag_datum(/datum/antagonist/rev)
if(rev)
rev.remove_revolutionary(FALSE, user)
if(bloody) //Apply blood
if(H.wear_mask)
H.wear_mask.add_mob_blood(H)
H.update_inv_wear_mask()
if(H.head)
H.head.add_mob_blood(H)
H.update_inv_head()
if(H.glasses && prob(33))
H.glasses.add_mob_blood(H)
H.update_inv_glasses()
if(BODY_ZONE_CHEST)
if(H.stat == CONSCIOUS && armor_block < 50)
if(prob(I.force))
H.visible_message("<span class='danger'>[H] has been knocked down!</span>", \
"<span class='userdanger'>[H] has been knocked down!</span>")
H.apply_effect(60, EFFECT_KNOCKDOWN, armor_block)
if(bloody)
if(H.wear_suit)
H.wear_suit.add_mob_blood(H)
H.update_inv_wear_suit()
if(H.w_uniform)
H.w_uniform.add_mob_blood(H)
H.update_inv_w_uniform()
if(Iforce > 10 || Iforce >= 5 && prob(33))
H.forcesay(GLOB.hit_appends) //forcesay checks stat already.
return TRUE
/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H)
var/hit_percent = (100-(blocked+armor))/100
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
if(!damage || hit_percent <= 0)
return 0
var/obj/item/bodypart/BP = null
if(isbodypart(def_zone))
BP = def_zone
else
if(!def_zone)
def_zone = ran_zone(def_zone)
BP = H.get_bodypart(check_zone(def_zone))
if(!BP)
BP = H.bodyparts[1]
switch(damagetype)
if(BRUTE)
H.damageoverlaytemp = 20
if(BP)
if(BP.receive_damage(damage * hit_percent * brutemod * H.physiology.brute_mod, 0))
H.update_damage_overlays()
else//no bodypart, we deal damage with a more general method.
H.adjustBruteLoss(damage * hit_percent * brutemod * H.physiology.brute_mod)
if(BURN)
H.damageoverlaytemp = 20
if(BP)
if(BP.receive_damage(0, damage * hit_percent * burnmod * H.physiology.burn_mod))
H.update_damage_overlays()
else
H.adjustFireLoss(damage * hit_percent * burnmod * H.physiology.burn_mod)
if(TOX)
H.adjustToxLoss(damage * hit_percent * H.physiology.tox_mod)
if(OXY)
H.adjustOxyLoss(damage * hit_percent * H.physiology.oxy_mod)
if(CLONE)
H.adjustCloneLoss(damage * hit_percent * H.physiology.clone_mod)
if(STAMINA)
if(BP)
if(BP.receive_damage(0, 0, damage * hit_percent * H.physiology.stamina_mod))
H.update_stamina()
else
H.adjustStaminaLoss(damage * hit_percent * H.physiology.stamina_mod)
if(BRAIN)
H.adjustBrainLoss(damage * hit_percent * H.physiology.brain_mod)
return 1
/datum/species/proc/on_hit(obj/item/projectile/P, mob/living/carbon/human/H)
// called when hit by a projectile
switch(P.type)
if(/obj/item/projectile/energy/floramut) // overwritten by plants/pods
H.show_message("<span class='notice'>The radiation beam dissipates harmlessly through your body.</span>")
if(/obj/item/projectile/energy/florayield)
H.show_message("<span class='notice'>The radiation beam dissipates harmlessly through your body.</span>")
/datum/species/proc/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H)
// called before a projectile hit
return 0
/////////////
//BREATHING//
/////////////
/datum/species/proc/breathe(mob/living/carbon/human/H)
if(H.has_trait(TRAIT_NOBREATH))
return TRUE
/datum/species/proc/handle_environment(datum/gas_mixture/environment, mob/living/carbon/human/H)
if(!environment)
return
if(istype(H.loc, /obj/machinery/atmospherics/components/unary/cryo_cell))
return
var/loc_temp = H.get_temperature(environment)
//Body temperature is adjusted in two parts: first there your body tries to naturally preserve homeostasis (shivering/sweating), then it reacts to the surrounding environment
//Thermal protection (insulation) has mixed benefits in two situations (hot in hot places, cold in hot places)
if(!H.on_fire) //If you're on fire, you do not heat up or cool down based on surrounding gases
var/natural = 0
if(H.stat != DEAD)
natural = H.natural_bodytemperature_stabilization()
var/thermal_protection = 1
if(loc_temp < H.bodytemperature) //Place is colder than we are
thermal_protection -= H.get_cold_protection(loc_temp) //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to.
if(H.bodytemperature < BODYTEMP_NORMAL) //we're cold, insulation helps us retain body heat and will reduce the heat we lose to the environment
H.adjust_bodytemperature((thermal_protection+1)*natural + max(thermal_protection * (loc_temp - H.bodytemperature) / BODYTEMP_COLD_DIVISOR, BODYTEMP_COOLING_MAX))
else //we're sweating, insulation hinders our ability to reduce heat - and it will reduce the amount of cooling you get from the environment
H.adjust_bodytemperature(natural*(1/(thermal_protection+1)) + max((thermal_protection * (loc_temp - H.bodytemperature) + BODYTEMP_NORMAL - H.bodytemperature) / BODYTEMP_COLD_DIVISOR , BODYTEMP_COOLING_MAX)) //Extra calculation for hardsuits to bleed off heat
else //Place is hotter than we are
thermal_protection -= H.get_heat_protection(loc_temp) //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to.
if(H.bodytemperature < BODYTEMP_NORMAL) //and we're cold, insulation enhances our ability to retain body heat but reduces the heat we get from the environment
H.adjust_bodytemperature((thermal_protection+1)*natural + min(thermal_protection * (loc_temp - H.bodytemperature) / BODYTEMP_HEAT_DIVISOR, BODYTEMP_HEATING_MAX))
else //we're sweating, insulation hinders out ability to reduce heat - but will reduce the amount of heat we get from the environment
H.adjust_bodytemperature(natural*(1/(thermal_protection+1)) + min(thermal_protection * (loc_temp - H.bodytemperature) / BODYTEMP_HEAT_DIVISOR, BODYTEMP_HEATING_MAX))
// +/- 50 degrees from 310K is the 'safe' zone, where no damage is dealt.
if(H.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT && !H.has_trait(TRAIT_RESISTHEAT))
//Body temperature is too hot.
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "cold")
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "hot", /datum/mood_event/hot)
var/burn_damage
switch(H.bodytemperature)
if(BODYTEMP_HEAT_DAMAGE_LIMIT to 400)
H.throw_alert("temp", /obj/screen/alert/hot, 1)
burn_damage = HEAT_DAMAGE_LEVEL_1
if(400 to 460)
H.throw_alert("temp", /obj/screen/alert/hot, 2)
burn_damage = HEAT_DAMAGE_LEVEL_2
else
H.throw_alert("temp", /obj/screen/alert/hot, 3)
if(H.on_fire)
burn_damage = HEAT_DAMAGE_LEVEL_3
else
burn_damage = HEAT_DAMAGE_LEVEL_2
burn_damage = burn_damage * heatmod * H.physiology.heat_mod
if (H.stat < UNCONSCIOUS && (prob(burn_damage) * 10) / 4) //40% for level 3 damage on humans
H.emote("scream")
H.apply_damage(burn_damage, BURN)
else if(H.bodytemperature < BODYTEMP_COLD_DAMAGE_LIMIT && !H.has_trait(TRAIT_RESISTCOLD))
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "hot")
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "cold", /datum/mood_event/cold)
switch(H.bodytemperature)
if(200 to BODYTEMP_COLD_DAMAGE_LIMIT)
H.throw_alert("temp", /obj/screen/alert/cold, 1)
H.apply_damage(COLD_DAMAGE_LEVEL_1*coldmod*H.physiology.cold_mod, BURN)
if(120 to 200)
H.throw_alert("temp", /obj/screen/alert/cold, 2)
H.apply_damage(COLD_DAMAGE_LEVEL_2*coldmod*H.physiology.cold_mod, BURN)
else
H.throw_alert("temp", /obj/screen/alert/cold, 3)
H.apply_damage(COLD_DAMAGE_LEVEL_3*coldmod*H.physiology.cold_mod, BURN)
else
H.clear_alert("temp")
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "cold")
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "hot")
var/pressure = environment.return_pressure()
var/adjusted_pressure = H.calculate_affecting_pressure(pressure) //Returns how much pressure actually affects the mob.
switch(adjusted_pressure)
if(HAZARD_HIGH_PRESSURE to INFINITY)
if(!H.has_trait(TRAIT_RESISTHIGHPRESSURE))
H.adjustBruteLoss(min(((adjusted_pressure / HAZARD_HIGH_PRESSURE) -1 ) * PRESSURE_DAMAGE_COEFFICIENT, MAX_HIGH_PRESSURE_DAMAGE) * H.physiology.pressure_mod)
H.throw_alert("pressure", /obj/screen/alert/highpressure, 2)
else
H.clear_alert("pressure")
if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE)
H.throw_alert("pressure", /obj/screen/alert/highpressure, 1)
if(WARNING_LOW_PRESSURE to WARNING_HIGH_PRESSURE)
H.clear_alert("pressure")
if(HAZARD_LOW_PRESSURE to WARNING_LOW_PRESSURE)
H.throw_alert("pressure", /obj/screen/alert/lowpressure, 1)
else
if(H.has_trait(TRAIT_RESISTLOWPRESSURE))
H.clear_alert("pressure")
else
H.adjustBruteLoss(LOW_PRESSURE_DAMAGE * H.physiology.pressure_mod)
H.throw_alert("pressure", /obj/screen/alert/lowpressure, 2)
//////////
// FIRE //
//////////
/datum/species/proc/handle_fire(mob/living/carbon/human/H, no_protection = FALSE)
if(H.has_trait(TRAIT_NOFIRE))
return
if(H.on_fire)
//the fire tries to damage the exposed clothes and items
var/list/burning_items = list()
//HEAD//
var/obj/item/clothing/head_clothes = null
if(H.glasses)
head_clothes = H.glasses
if(H.wear_mask)
head_clothes = H.wear_mask
if(H.wear_neck)
head_clothes = H.wear_neck
if(H.head)
head_clothes = H.head
if(head_clothes)
burning_items += head_clothes
else if(H.ears)
burning_items += H.ears
//CHEST//
var/obj/item/clothing/chest_clothes = null
if(H.w_uniform)
chest_clothes = H.w_uniform
if(H.wear_suit)
chest_clothes = H.wear_suit
if(chest_clothes)
burning_items += chest_clothes
//ARMS & HANDS//
var/obj/item/clothing/arm_clothes = null
if(H.gloves)
arm_clothes = H.gloves
if(H.w_uniform && ((H.w_uniform.body_parts_covered & HANDS) || (H.w_uniform.body_parts_covered & ARMS)))
arm_clothes = H.w_uniform
if(H.wear_suit && ((H.wear_suit.body_parts_covered & HANDS) || (H.wear_suit.body_parts_covered & ARMS)))
arm_clothes = H.wear_suit
if(arm_clothes)
burning_items |= arm_clothes
//LEGS & FEET//
var/obj/item/clothing/leg_clothes = null
if(H.shoes)
leg_clothes = H.shoes
if(H.w_uniform && ((H.w_uniform.body_parts_covered & FEET) || (H.w_uniform.body_parts_covered & LEGS)))
leg_clothes = H.w_uniform
if(H.wear_suit && ((H.wear_suit.body_parts_covered & FEET) || (H.wear_suit.body_parts_covered & LEGS)))
leg_clothes = H.wear_suit
if(leg_clothes)
burning_items |= leg_clothes
for(var/X in burning_items)
var/obj/item/I = X
if(!(I.resistance_flags & FIRE_PROOF))
I.take_damage(H.fire_stacks, BURN, "fire", 0)
var/thermal_protection = H.get_thermal_protection()
if(thermal_protection >= FIRE_IMMUNITY_SUIT_MAX_TEMP_PROTECT && !no_protection)
return
if(thermal_protection >= FIRE_SUIT_MAX_TEMP_PROTECT && !no_protection)
H.adjust_bodytemperature(11)
else
H.adjust_bodytemperature(BODYTEMP_HEATING_MAX + (H.fire_stacks * 12))
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire)
/datum/species/proc/CanIgniteMob(mob/living/carbon/human/H)
if(H.has_trait(TRAIT_NOFIRE))
return FALSE
return TRUE
/datum/species/proc/ExtinguishMob(mob/living/carbon/human/H)
return
////////////
//Stun//
////////////
/datum/species/proc/spec_stun(mob/living/carbon/human/H,amount)
. = stunmod * H.physiology.stun_mod * amount
//////////////
//Space Move//
//////////////
/datum/species/proc/space_move(mob/living/carbon/human/H)
return 0
/datum/species/proc/negates_gravity(mob/living/carbon/human/H)
return 0