From 8a7f258171818135dd1fab462e510ca00e33cde6 Mon Sep 17 00:00:00 2001 From: Time-Green Date: Thu, 8 Jul 2021 17:27:19 +0200 Subject: [PATCH] The Great Species Dedatumming: External feature organs, part 1 (#59981) Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> --- code/__DEFINES/DNA.dm | 10 + code/__DEFINES/dcs/signals.dm | 2 + code/__DEFINES/misc.dm | 11 + code/__DEFINES/traits.dm | 2 + code/datums/dna.dm | 9 +- .../mining/lavaland/necropolis_chests.dm | 8 +- code/modules/mob/living/carbon/human/emote.dm | 29 +- .../mob/living/carbon/human/human_movement.dm | 4 +- .../living/carbon/human/human_update_icons.dm | 6 + .../mob/living/carbon/human/species.dm | 185 ++----------- .../human/species_types/lizardpeople.dm | 6 +- .../carbon/human/species_types/mothmen.dm | 47 +--- code/modules/mob/living/emote.dm | 11 +- .../surgery/advanced/wingreconstruction.dm | 16 +- code/modules/surgery/bodyparts/_bodyparts.dm | 15 +- .../stomach/external/_external_organs.dm | 261 ++++++++++++++++++ .../surgery/organs/stomach/external/wings.dm | 229 +++++++++++++++ tgstation.dme | 2 + 18 files changed, 600 insertions(+), 253 deletions(-) create mode 100644 code/modules/surgery/organs/stomach/external/_external_organs.dm create mode 100644 code/modules/surgery/organs/stomach/external/wings.dm diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm index 26c503fe6c4..c8f2129a63d 100644 --- a/code/__DEFINES/DNA.dm +++ b/code/__DEFINES/DNA.dm @@ -174,6 +174,16 @@ #define ORGAN_SLOT_VOICE "vocal_cords" #define ORGAN_SLOT_ZOMBIE "zombie_infection" +/// Organ slot external +#define ORGAN_SLOT_EXTERNAL_TAIL "tail" +#define ORGAN_SLOT_EXTERNAL_SPINES "spines" +#define ORGAN_SLOT_EXTERNAL_SNOUT "snout" +#define ORGAN_SLOT_EXTERNAL_FRILLS "frills" +#define ORGAN_SLOT_EXTERNAL_HORNS "horns" +#define ORGAN_SLOT_EXTERNAL_WINGS "wings" +#define ORGAN_SLOT_EXTERNAL_ANTENNAE "antennae" +#define ORGAN_SLOT_EXTERNAL_BODYMARKINGS "bodymarkings" + /// Xenomorph organ slots #define ORGAN_SLOT_XENO_ACIDGLAND "acid_gland" #define ORGAN_SLOT_XENO_EGGSAC "eggsac" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index c71232b6d69..77a6f7ae808 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -987,6 +987,8 @@ #define COMSIG_JOB_RECEIVED "job_received" ///from /mob/living/carbon/human/proc/set_coretemperature(): (oldvalue, newvalue) #define COMSIG_HUMAN_CORETEMP_CHANGE "human_coretemp_change" +///from /datum/species/handle_fire. Called when the human is set on fire and burning clothes and stuff +#define COMSIG_HUMAN_BURNING "human_burning" // /datum/species signals diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 6f2a79583c6..7fe8554fb02 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -44,6 +44,17 @@ #define FIRE_LAYER 1 //If you're on fire #define TOTAL_LAYERS 29 //KEEP THIS UP-TO-DATE OR SHIT WILL BREAK ;_; +//Bitflags for the layers an external organ can draw on +#define EXTERNAL_FRONT (1 << 1) +#define EXTERNAL_ADJACENT (1 << 2) +#define EXTERNAL_BEHIND (1 << 3) +#define ALL_EXTERNAL_OVERLAYS EXTERNAL_FRONT | EXTERNAL_ADJACENT | EXTERNAL_BEHIND + +//The layer external organs draw. These are drawn on the limbs, so the layers are relative to the limb theyre being drawn on +#define EXTERNAL_FRONT_LAYER 2 +#define EXTERNAL_ADJACENT_LAYER 1 +#define EXTERNAL_BEHIND_LAYER -1 + //Human Overlay Index Shortcuts for alternate_worn_layer, layers //Because I *KNOW* somebody will think layer+1 means "above" //IT DOESN'T OK, IT MEANS "UNDER" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 74d1ec23f83..95f48e0ab86 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -329,6 +329,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed /// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met #define TRAIT_AI_BAGATTACK "bagattack" +///When people are floating from zero-grav or something, we can move around freely! +#define TRAIT_FREE_FLOAT_MOVEMENT "free_float_movement" // You can stare into the abyss, but it does not stare back. // You're immune to the hallucination effect of the supermatter, either diff --git a/code/datums/dna.dm b/code/datums/dna.dm index e7dcd51001d..cc5ccb49936 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -520,13 +520,11 @@ if(dna.features["moth_wings"]) var/genetic_value = GLOB.moth_wings_list[deconstruct_block(getblock(features, DNA_MOTH_WINGS_BLOCK), GLOB.moth_wings_list.len)] dna.features["original_moth_wings"] = genetic_value - if(dna.features["moth_wings"] != "Burnt Off") - dna.features["moth_wings"] = genetic_value + dna.features["moth_wings"] = genetic_value if(dna.features["moth_antennae"]) var/genetic_value = GLOB.moth_antennae_list[deconstruct_block(getblock(features, DNA_MOTH_ANTENNAE_BLOCK), GLOB.moth_antennae_list.len)] dna.features["original_moth_antennae"] = genetic_value - if(dna.features["moth_antennae"] != "Burnt Off") - dna.features["moth_antennae"] = genetic_value + dna.features["moth_antennae"] = genetic_value if(dna.features["moth_markings"]) dna.features["moth_markings"] = GLOB.moth_markings_list[deconstruct_block(getblock(features, DNA_MOTH_MARKINGS_BLOCK), GLOB.moth_markings_list.len)] if(dna.features["caps"]) @@ -534,6 +532,9 @@ if(dna.features["tail_monkey"]) dna.features["tail_monkey"] = GLOB.tails_list_monkey[deconstruct_block(getblock(features, DNA_MONKEY_TAIL_BLOCK), GLOB.tails_list_monkey.len)] + for(var/obj/item/organ/external/external_organ in internal_organs) + external_organ.mutate_feature(features, src) + if(icon_update) dna.species.handle_body(src) // We want 'update_body_parts()' to be called only if mutcolor_update is TRUE, so no 'update_body()' here. update_hair() diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index d186cbdd2ee..4cb61f1028d 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -630,18 +630,12 @@ if(iscarbon(exposed_mob) && exposed_mob.stat != DEAD) var/mob/living/carbon/exposed_carbon = exposed_mob var/holycheck = ishumanbasic(exposed_carbon) - if(reac_volume < 5 || !(holycheck || islizard(exposed_carbon) || (ismoth(exposed_carbon) && exposed_carbon.dna.features["moth_wings"] != "Burnt Off"))) // implying xenohumans are holy //as with all things, + if(reac_volume < 5 || !(holycheck || islizard(exposed_carbon) || ismoth(exposed_carbon))) // implying xenohumans are holy //as with all things, if((methods & INGEST) && show_message) to_chat(exposed_carbon, span_notice("You feel nothing but a terrible aftertaste.")) return if(exposed_carbon.dna.species.has_innate_wings) to_chat(exposed_carbon, span_userdanger("A terrible pain travels down your back as your wings change shape!")) - if(!exposed_carbon.dna.features["original_moth_wings"]) //Stores their wings for later possible reconstruction - exposed_carbon.dna.features["original_moth_wings"] = exposed_carbon.dna.features["moth_wings"] - exposed_carbon.dna.features["moth_wings"] = "None" - if(!exposed_carbon.dna.features["original_moth_antennae"]) //Stores their antennae type as well - exposed_carbon.dna.features["original_moth_antennae"] = exposed_carbon.dna.features["moth_antennae"] - exposed_carbon.dna.features["moth_antennae"] = "Regal" else to_chat(exposed_carbon, span_userdanger("A terrible pain travels down your back as wings burst out!")) exposed_carbon.dna.species.GiveSpeciesFlight(exposed_carbon) diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index bead0dcf46d..13307697dc3 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -150,10 +150,11 @@ . = ..() if(.) var/mob/living/carbon/human/H = user - if(findtext(select_message_type(user,intentional), "open")) - H.OpenWings() + var/obj/item/organ/external/wings/functional/wings = H.getorganslot(ORGAN_SLOT_EXTERNAL_WINGS) + if(wings && findtext(select_message_type(user,intentional), "open")) + wings.open_wings() else - H.CloseWings() + wings.close_wings() /datum/emote/living/carbon/human/wing/select_message_type(mob/user, intentional) . = ..() @@ -170,28 +171,6 @@ if(H.dna && H.dna.species && (H.dna.features["wings"] != "None")) return TRUE -/mob/living/carbon/human/proc/OpenWings() - if(!dna || !dna.species) - return - if(dna.species.mutant_bodyparts["wings"]) - dna.species.mutant_bodyparts["wingsopen"] = dna.species.mutant_bodyparts["wings"] - dna.species.mutant_bodyparts -= "wings" - update_body() - -/mob/living/carbon/human/proc/CloseWings() - if(!dna || !dna.species) - return - if(dna.species.mutant_bodyparts["wingsopen"]) - dna.species.mutant_bodyparts["wings"] = dna.species.mutant_bodyparts["wingsopen"] - dna.species.mutant_bodyparts -= "wingsopen" - update_body() - if(isturf(loc)) - var/turf/T = loc - T.Entered(src, null) - -//Ayy lmao - - ///Snowflake emotes only for le epic chimp /datum/emote/living/carbon/human/monkey diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index ef518022e3c..257f5d6cb33 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -48,7 +48,7 @@ if(shoes && body_position == STANDING_UP && loc == NewLoc && has_gravity(loc)) SEND_SIGNAL(shoes, COMSIG_SHOES_STEP_ACTION) -/mob/living/carbon/human/Process_Spacemove(movement_dir = 0) //Temporary laziness thing. Will change to handles by species reee. - if(dna.species.space_move(src)) +/mob/living/carbon/human/Process_Spacemove(movement_dir = 0) + if(movement_type & FLYING || HAS_TRAIT(src, TRAIT_FREE_FLOAT_MOVEMENT)) return TRUE return ..() diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index 9629800a554..82101d21e68 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -55,6 +55,7 @@ There are several things that need to be remembered: //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() dna.species.handle_mutant_bodyparts(src) + update_body_parts() // basically a better and cooler handle_mutant_bodyparts (at least until handle_mutant_bodyparts is annihilated) /mob/living/carbon/human/update_body() @@ -588,6 +589,11 @@ generate/load female uniform sprites matching all previously decided variables for(var/X in bodyparts) var/obj/item/bodypart/BP = X . += "-[BP.body_zone]" + + for(var/obj/item/organ/external/organ in BP.external_organs) + if(organ.can_draw_on_bodypart(src)) //make sure we're drawn before generating a key + . += "([organ.cache_key])" + if(BP.status == BODYPART_ORGANIC) . += "-organic" else diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 9827f5f8170..cf06b7cb1c5 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -78,6 +78,10 @@ GLOBAL_LIST_EMPTY(roundstart_races) BODY_ZONE_L_LEG = /obj/item/bodypart/l_leg,\ BODY_ZONE_R_LEG = /obj/item/bodypart/r_leg,\ BODY_ZONE_CHEST = /obj/item/bodypart/chest) + + ///List of external organs to generate like horns, frills, wings, etc. list(typepath of organ = "Round Beautiful BDSM Snout"). Still WIP + var/list/external_organs = list() + ///Multiplier for the race's speed. Positive numbers make it move slower, negative numbers make it move faster. var/speedmod = 0 ///Percentage modifier for overall defense of the race, or less defense, if it's negative. @@ -123,8 +127,6 @@ GLOBAL_LIST_EMPTY(roundstart_races) var/flying_species = FALSE ///The actual flying ability given to flying species var/datum/action/innate/flight/fly - ///Current wings icon - var/wings_icon = "Angel" //Dictates which wing icons are allowed for a given species. If count is >1 a radial menu is used to choose between all icons in list var/list/wings_icons = list("Angel") ///Used to determine what description to give when using a potion of flight, if false it will describe them as growing new wings @@ -188,6 +190,8 @@ GLOBAL_LIST_EMPTY(roundstart_races) ///Forces an item into this species' hands. Only an honorary mutantthing because this is not an organ and not loaded in the same way, you've been warned to do your research. var/obj/item/mutanthands + + ///Bitflag that controls what in game ways something can select this species as a spawnable source, such as magic mirrors. See [mob defines][code/__DEFINES/mobs.dm] for possible sources. var/changesource_flags = NONE @@ -400,6 +404,16 @@ GLOBAL_LIST_EMPTY(roundstart_races) 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()) + if(ishuman(C)) + var/mob/living/carbon/human/human = C + for(var/obj/item/organ/external/organ_path as anything in external_organs) + //Load a persons preferences from DNA + var/preference_name = human.dna.features[initial(organ_path.preference)] + + var/obj/item/organ/external/new_organ = new organ_path(null, preference_name, human.body_type) + + new_organ.Insert(human) + for(var/X in inherent_traits) ADD_TRAIT(C, X, SPECIES_TRAIT) @@ -445,6 +459,10 @@ GLOBAL_LIST_EMPTY(roundstart_races) C.Digitigrade_Leg_Swap(TRUE) for(var/X in inherent_traits) REMOVE_TRAIT(C, X, SPECIES_TRAIT) + for(var/obj/item/organ/external/organ in C.internal_organs) + if(organ.type in external_organs) + organ.Remove(C) + qdel(organ) //If their inert mutation is not the same, swap it out if((inert_mutation != new_species.inert_mutation) && LAZYLEN(C.dna.mutation_index) && (inert_mutation in C.dna.mutation_index)) @@ -460,15 +478,6 @@ GLOBAL_LIST_EMPTY(roundstart_races) for(var/i in inherent_factions) C.faction -= i - if(flying_species) - fly.Remove(C) - QDEL_NULL(fly) - if(C.movement_type & FLYING) - ToggleFlight(C) - if(C.dna && C.dna.species && (C.dna.features["wings"] == wings_icon)) - C.dna.species.mutant_bodyparts -= "wings" - C.dna.features["wings"] = "None" - C.update_body() clear_tail_moodlets(C) C.remove_movespeed_modifier(/datum/movespeed_modifier/species) @@ -816,7 +825,6 @@ GLOBAL_LIST_EMPTY(roundstart_races) if(source.wear_suit && (source.wear_suit.flags_inv & HIDEJUMPSUIT)) bodyparts_to_add -= "tail_monkey" - if(mutant_bodyparts["waggingtail_human"]) if(source.wear_suit && (source.wear_suit.flags_inv & HIDEJUMPSUIT)) bodyparts_to_add -= "waggingtail_human" @@ -833,36 +841,10 @@ GLOBAL_LIST_EMPTY(roundstart_races) else if (mutant_bodyparts["tail"]) bodyparts_to_add -= "waggingspines" - if(mutant_bodyparts["snout"]) //Take a closer look at that snout! - if((source.wear_mask && (source.wear_mask.flags_inv & HIDESNOUT)) || (source.head && (source.head.flags_inv & HIDESNOUT)) || !noggin || noggin.status == BODYPART_ROBOTIC) - bodyparts_to_add -= "snout" - - if(mutant_bodyparts["frills"]) - if(!source.dna.features["frills"] || source.dna.features["frills"] == "None" || source.head && (source.head.flags_inv & HIDEEARS) || !noggin || noggin.status == BODYPART_ROBOTIC) - bodyparts_to_add -= "frills" - - if(mutant_bodyparts["horns"]) - if(!source.dna.features["horns"] || source.dna.features["horns"] == "None" || source.head && (source.head.flags_inv & HIDEHAIR) || (source.wear_mask && (source.wear_mask.flags_inv & HIDEHAIR)) || !noggin || noggin.status == BODYPART_ROBOTIC) - bodyparts_to_add -= "horns" - if(mutant_bodyparts["ears"]) if(!source.dna.features["ears"] || source.dna.features["ears"] == "None" || source.head && (source.head.flags_inv & HIDEHAIR) || (source.wear_mask && (source.wear_mask.flags_inv & HIDEHAIR)) || !noggin || noggin.status == BODYPART_ROBOTIC) bodyparts_to_add -= "ears" - if(mutant_bodyparts["wings"]) - if(!source.dna.features["wings"] || source.dna.features["wings"] == "None" || (source.wear_suit && (source.wear_suit.flags_inv & HIDEJUMPSUIT) && (!source.wear_suit.species_exception || !is_type_in_list(src, source.wear_suit.species_exception)))) - bodyparts_to_add -= "wings" - - if(mutant_bodyparts["wings_open"]) - if(source.wear_suit && (source.wear_suit.flags_inv & HIDEJUMPSUIT) && (!source.wear_suit.species_exception || !is_type_in_list(src, source.wear_suit.species_exception))) - bodyparts_to_add -= "wings_open" - else if (mutant_bodyparts["wings"]) - bodyparts_to_add -= "wings_open" - - if(mutant_bodyparts["moth_antennae"]) - if(!source.dna.features["moth_antennae"] || source.dna.features["moth_antennae"] == "None" || !noggin) - bodyparts_to_add -= "moth_antennae" - //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 @@ -909,26 +891,12 @@ GLOBAL_LIST_EMPTY(roundstart_races) accessory = GLOB.spines_list[source.dna.features["spines"]] if("waggingspines") accessory = GLOB.animated_spines_list[source.dna.features["spines"]] - if("snout") - accessory = GLOB.snouts_list[source.dna.features["snout"]] - if("frills") - accessory = GLOB.frills_list[source.dna.features["frills"]] - if("horns") - accessory = GLOB.horns_list[source.dna.features["horns"]] if("ears") accessory = GLOB.ears_list[source.dna.features["ears"]] if("body_markings") accessory = GLOB.body_markings_list[source.dna.features["body_markings"]] - if("wings") - accessory = GLOB.wings_list[source.dna.features["wings"]] - if("wingsopen") - accessory = GLOB.wings_open_list[source.dna.features["wings"]] if("legs") accessory = GLOB.legs_list[source.dna.features["legs"]] - if("moth_wings") - accessory = GLOB.moth_wings_list[source.dna.features["moth_wings"]] - if("moth_antennae") - accessory = GLOB.moth_antennae_list[source.dna.features["moth_antennae"]] if("caps") accessory = GLOB.caps_list[source.dna.features["caps"]] if("tail_monkey") @@ -999,7 +967,6 @@ GLOBAL_LIST_EMPTY(roundstart_races) source.apply_overlay(BODY_ADJ_LAYER) source.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) @@ -1031,8 +998,6 @@ GLOBAL_LIST_EMPTY(roundstart_races) var/takes_crit_damage = (!HAS_TRAIT(H, TRAIT_NOCRITDAMAGE)) if((H.health < H.crit_threshold) && takes_crit_damage && H.stat != DEAD) H.adjustBruteLoss(0.5 * delta_time) - if(flying_species) - HandleFlight(H) /datum/species/proc/spec_death(gibbed, mob/living/carbon/human/H) return @@ -1893,6 +1858,7 @@ GLOBAL_LIST_EMPTY(roundstart_races) if(!CanIgniteMob(H)) return TRUE if(H.on_fire) + SEND_SIGNAL(H, COMSIG_HUMAN_BURNING) //the fire tries to damage the exposed clothes and items var/list/burning_items = list() var/obscured = H.check_obscured_slots(TRUE) @@ -1965,19 +1931,12 @@ GLOBAL_LIST_EMPTY(roundstart_races) //////////// /datum/species/proc/spec_stun(mob/living/carbon/human/H,amount) - if(flying_species && H.movement_type & FLYING) - ToggleFlight(H) - flyslip(H) - . = stunmod * H.physiology.stun_mod * amount - -////////////// -//Space Move// -////////////// - -/datum/species/proc/space_move(mob/living/carbon/human/H) if(H.movement_type & FLYING) - return TRUE - return FALSE + var/obj/item/organ/external/wings/functional/wings = H.getorganslot(ORGAN_SLOT_EXTERNAL_WINGS) + if(wings) + wings.toggle_flight(H) + wings.fly_slip(H) + . = stunmod * H.physiology.stun_mod * amount /datum/species/proc/negates_gravity(mob/living/carbon/human/H) if(H.movement_type & FLYING) @@ -2063,6 +2022,7 @@ GLOBAL_LIST_EMPTY(roundstart_races) if(flying_species) //species that already have flying traits should not work with this proc return flying_species = TRUE + var/wings_icon if(wings_icons.len > 1) if(!H.client) wings_icon = pick(wings_icons) @@ -2082,95 +2042,10 @@ GLOBAL_LIST_EMPTY(roundstart_races) wings_icon = pick(wings_icons) else wings_icon = wings_icons[1] - if(isnull(fly)) - fly = new - fly.Grant(H) - if(H.dna.features["wings"] != wings_icon) - mutant_bodyparts["wings"] = wings_icon - H.dna.features["wings"] = wings_icon - H.update_body() -/datum/species/proc/HandleFlight(mob/living/carbon/human/H) - if(H.movement_type & FLYING) - if(!CanFly(H)) - ToggleFlight(H) - return FALSE - return TRUE - else - return FALSE - -/datum/species/proc/CanFly(mob/living/carbon/human/H) - if(H.stat || H.body_position == LYING_DOWN) - return FALSE - 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)))) //Jumpsuits have tail holes, so it makes sense they have wing holes too - to_chat(H, span_warning("Your suit blocks your wings from extending!")) - return FALSE - var/turf/T = get_turf(H) - if(!T) - return FALSE - - var/datum/gas_mixture/environment = T.return_air() - if(environment && !(environment.return_pressure() > 30)) - to_chat(H, span_warning("The atmosphere is too thin for you to fly!")) - return FALSE - else - return TRUE - -/datum/species/proc/flyslip(mob/living/carbon/human/H) - var/obj/buckled_obj - if(H.buckled) - buckled_obj = H.buckled - - to_chat(H, span_notice("Your wings spazz out and launch you!")) - - playsound(H.loc, 'sound/misc/slip.ogg', 50, TRUE, -3) - - for(var/obj/item/I in H.held_items) - H.accident(I) - - var/olddir = H.dir - - H.stop_pulling() - if(buckled_obj) - buckled_obj.unbuckle_mob(H) - step(buckled_obj, olddir) - else - new /datum/forced_movement(H, get_ranged_target_turf(H, olddir, 4), 1, FALSE, CALLBACK(H, /mob/living/carbon/.proc/spin, 1, 1)) - return TRUE - -//UNSAFE PROC, should only be called through the Activate or other sources that check for CanFly -/datum/species/proc/ToggleFlight(mob/living/carbon/human/H) - if(!HAS_TRAIT_FROM(H, TRAIT_MOVE_FLYING, SPECIES_FLIGHT_TRAIT)) - stunmod *= 2 - speedmod -= 0.35 - ADD_TRAIT(H, TRAIT_NO_FLOATING_ANIM, SPECIES_FLIGHT_TRAIT) - ADD_TRAIT(H, TRAIT_MOVE_FLYING, SPECIES_FLIGHT_TRAIT) - passtable_on(H, SPECIES_TRAIT) - H.OpenWings() - else - stunmod *= 0.5 - speedmod += 0.35 - REMOVE_TRAIT(H, TRAIT_NO_FLOATING_ANIM, SPECIES_FLIGHT_TRAIT) - REMOVE_TRAIT(H, TRAIT_MOVE_FLYING, SPECIES_FLIGHT_TRAIT) - passtable_off(H, SPECIES_TRAIT) - H.CloseWings() - -/datum/action/innate/flight - name = "Toggle Flight" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_IMMOBILE - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "flight" - -/datum/action/innate/flight/Activate() - var/mob/living/carbon/human/H = owner - var/datum/species/S = H.dna.species - if(S.CanFly(H)) - S.ToggleFlight(H) - if(!(H.movement_type & FLYING)) - to_chat(H, span_notice("You settle gently back onto the ground...")) - else - to_chat(H, span_notice("You beat your wings and begin to hover gently above the ground...")) - H.set_resting(FALSE, TRUE) + var/obj/item/organ/external/wings/functional/wings = new(null, wings_icon, H.body_type) + wings.Insert(H) + handle_mutant_bodyparts(H) /** * The human species version of [/mob/living/carbon/proc/get_biological_state]. Depends on the HAS_FLESH and HAS_BONE species traits, having bones lets you have bone wounds, having flesh lets you have burn, slash, and piercing wounds diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index a4cca830e17..896e4c941a3 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -6,8 +6,10 @@ default_color = "00FF00" species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAS_FLESH,HAS_BONE) inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_REPTILE - mutant_bodyparts = list("tail_lizard" = "Smooth", "snout" = "Round", "horns" = "None", - "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs") + mutant_bodyparts = list("tail_lizard" = "Smooth", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs") + external_organs = list(/obj/item/organ/external/horns = "None", + /obj/item/organ/external/frills = "None", + /obj/item/organ/external/snout = "Round") mutanttongue = /obj/item/organ/tongue/lizard mutant_organs = list(/obj/item/organ/tail/lizard) coldmod = 1.5 diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index bb4fc4db5d6..312d11e5c17 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -5,7 +5,8 @@ default_color = "00FF00" species_traits = list(LIPS, HAS_FLESH, HAS_BONE, HAS_MARKINGS, TRAIT_ANTENNAE) inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG - mutant_bodyparts = list("moth_wings" = "Plain", "moth_antennae" = "Plain", "moth_markings" = "None") + mutant_bodyparts = list("moth_markings" = "None") + external_organs = list(/obj/item/organ/external/wings/moth = "Plain", /obj/item/organ/external/antennae = "Plain") attack_verb = "slash" attack_effect = ATTACK_EFFECT_CLAW attack_sound = 'sound/weapons/slash.ogg' @@ -39,28 +40,6 @@ return randname -/datum/species/moth/handle_fire(mob/living/carbon/human/H, delta_time, times_fired, no_protection = FALSE) - . = ..() - if(.) //if the mob is immune to fire, don't burn wings off. - return - if(H.dna.features["moth_wings"] != "Burnt Off" && H.bodytemperature >= 800 && H.fire_stacks > 0) //do not go into the extremely hot light. you will not survive - to_chat(H, span_danger("Your precious wings burn to a crisp!")) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "burnt_wings", /datum/mood_event/burnt_wings) - if(!H.dna.features["original_moth_wings"]) //Fire apparently destroys DNA, so let's preserve that elsewhere, checks if an original was already stored to prevent bugs - H.dna.features["original_moth_wings"] = H.dna.features["moth_wings"] - H.dna.features["moth_wings"] = "Burnt Off" - if(!H.dna.features["original_moth_antennae"]) //Stores antennae type for if they get restored later - H.dna.features["original_moth_antennae"] = H.dna.features["moth_antennae"] - H.dna.features["moth_antennae"] = "Burnt Off" - if(flying_species) //This is all exclusive to if the person has the effects of a potion of flight - if(H.movement_type & FLYING) - ToggleFlight(H) - H.Knockdown(1.5 SECONDS) - fly.Remove(H) - QDEL_NULL(fly) - H.dna.features["wings"] = "None" - handle_mutant_bodyparts(H) - /datum/species/moth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H, delta_time, times_fired) . = ..() if(chem.type == /datum/reagent/toxin/pestkiller) @@ -72,13 +51,6 @@ return 10 //flyswatters deal 10x damage to moths return 1 -/datum/species/moth/space_move(mob/living/carbon/human/H) - . = ..() - if(H.loc && !isspaceturf(H.loc) && H.dna.features["moth_wings"] != "Burnt Off" && !flying_species) //"flying_species" is exclusive to the potion of flight, which has its flying mechanics. If they want to fly they can use that instead - var/datum/gas_mixture/current = H.loc.return_air() - if(current && (current.return_pressure() >= ONE_ATMOSPHERE*0.85)) //as long as there's reasonable pressure and no gravity, flight is possible - return TRUE - /datum/species/moth/randomize_main_appearance_element(mob/living/carbon/human/human_mob) var/wings = pick(GLOB.moth_wings_list) mutant_bodyparts["wings"] = wings @@ -86,18 +58,3 @@ human_mob.dna.features["wings"] = wings human_mob.dna.features["moth_wings"] = wings human_mob.update_body() - -/datum/species/moth/spec_fully_heal(mob/living/carbon/human/H) - . = ..() - if(H.dna.features["original_moth_wings"] != null) - H.dna.features["moth_wings"] = H.dna.features["original_moth_wings"] - - if(H.dna.features["original_moth_wings"] == null && H.dna.features["moth_wings"] == "Burnt Off") - H.dna.features["moth_wings"] = "Plain" - - if(H.dna.features["original_moth_antennae"] != null) - H.dna.features["moth_antennae"] = H.dna.features["original_moth_antennae"] - - if(H.dna.features["original_moth_antennae"] == null && H.dna.features["moth_antennae"] == "Burnt Off") - H.dna.features["moth_antennae"] = "Plain" - handle_mutant_bodyparts(H) diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 972435072e1..22ef1fbdc3f 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -123,13 +123,14 @@ if(. && ishuman(user)) var/mob/living/carbon/human/H = user var/open = FALSE - if(H.dna.features["wings"] != "None") - if(H.dna.species.mutant_bodyparts["wingsopen"]) + var/obj/item/organ/external/wings/functional/wings = H.getorganslot(ORGAN_SLOT_EXTERNAL_WINGS) + if(wings) + if(wings.wings_open) open = TRUE - H.CloseWings() + wings.close_wings() else - H.OpenWings() - addtimer(CALLBACK(H, open ? /mob/living/carbon/human.proc/OpenWings : /mob/living/carbon/human.proc/CloseWings), wing_time) + wings.open_wings() + addtimer(CALLBACK(wings, open ? /obj/item/organ/external/wings/functional.proc/open_wings : /obj/item/organ/external/wings/functional.proc/close_wings), wing_time) /datum/emote/living/flap/aflap key = "aflap" diff --git a/code/modules/surgery/advanced/wingreconstruction.dm b/code/modules/surgery/advanced/wingreconstruction.dm index c1ff298de1d..bcf047fe7ba 100644 --- a/code/modules/surgery/advanced/wingreconstruction.dm +++ b/code/modules/surgery/advanced/wingreconstruction.dm @@ -12,7 +12,8 @@ /datum/surgery/advanced/wing_reconstruction/can_start(mob/user, mob/living/carbon/target) if(!istype(target)) return FALSE - return ..() && target.dna.features["moth_wings"] == "Burnt Off" && ismoth(target) + var/obj/item/organ/external/wings/moth/wings = target.getorganslot(ORGAN_SLOT_EXTERNAL_WINGS) + return ..() && wings?.burnt /datum/surgery_step/wing_reconstruction name = "start wing reconstruction" @@ -35,10 +36,11 @@ display_results(user, target, span_notice("You succeed in reconstructing [target]'s wings."), span_notice("[user] successfully reconstructs [target]'s wings!"), span_notice("[user] completes the surgery on [target]'s wings.")) - if(human_target.dna.features["original_moth_wings"] != null) - human_target.dna.features["moth_wings"] = human_target.dna.features["original_moth_wings"] - else - human_target.dna.features["moth_wings"] = "Plain" - human_target.dna.update_uf_block(DNA_MOTH_WINGS_BLOCK) - human_target.update_mutant_bodyparts() + var/obj/item/organ/external/wings/moth/wings = target.getorganslot(ORGAN_SLOT_EXTERNAL_WINGS) + wings?.heal_wings() + + var/obj/item/organ/external/antennae/antennae = target.getorganslot(ORGAN_SLOT_EXTERNAL_ANTENNAE) //i mean we might aswell heal their antennae too + antennae?.heal_antennae() + + human_target.update_body_parts() return ..() diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index b3b3ae199de..93a55cc9063 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -105,6 +105,9 @@ /// If something is currently grasping this bodypart and trying to staunch bleeding (see [/obj/item/self_grasp]) var/obj/item/self_grasp/grasped_by + ///A list of all the external organs we've got stored to draw horns, wings and stuff with (special because we are actually in the limbs unlike normal organs :/ ) + var/list/obj/item/organ/external/external_organs = list() + /obj/item/bodypart/Initialize(mapload) . = ..() @@ -889,8 +892,9 @@ aux = image(limb.icon, "[species_id]_[aux_zone]", -aux_layer, image_dir) . += aux + var/draw_color if(should_draw_greyscale) - var/draw_color = mutation_color || species_color || (skin_tone && skintone2hex(skin_tone)) + draw_color = mutation_color || species_color || (skin_tone && skintone2hex(skin_tone)) if(draw_color) limb.color = "#[draw_color]" if(aux_zone) @@ -908,6 +912,15 @@ aux_em_block.color = GLOB.em_block_color aux.overlays += aux_em_block + //Draw external organs like horns and frills + for(var/obj/item/organ/external/external_organ in external_organs) + if(!dropped && !external_organ.can_draw_on_bodypart(owner)) + continue + //Some externals have multiple layers for background, foreground and between + for(var/external_layer in external_organ.all_layers) + if(external_organ.layers & external_layer) + external_organ.get_overlays(., image_dir, external_organ.bitflag_to_layer(external_layer), icon_gender, "#[draw_color]") + /obj/item/bodypart/deconstruct(disassembled = TRUE) drop_organs() qdel(src) diff --git a/code/modules/surgery/organs/stomach/external/_external_organs.dm b/code/modules/surgery/organs/stomach/external/_external_organs.dm new file mode 100644 index 00000000000..91f8fcba37a --- /dev/null +++ b/code/modules/surgery/organs/stomach/external/_external_organs.dm @@ -0,0 +1,261 @@ +/** +* System for drawing organs with overlays. These overlays are drawn directly on the bodypart, attached to a person or not +* Works in tandem with the /datum/sprite_accessory datum to generate sprites +* Unlike normal organs, we're actually inside a persons limbs at all times +*/ +/obj/item/organ/external + name = "external organ" + desc = "An external organ that is too external." + + ///Unremovable is until the features are completely finished + organ_flags = ORGAN_UNREMOVABLE | ORGAN_EDIBLE + + ///Sometimes we need multiple layers, for like the back, middle and front of the person + var/layers + ///Convert the bitflag define into the actual layer define + var/static/list/all_layers = list(EXTERNAL_FRONT, EXTERNAL_ADJACENT, EXTERNAL_BEHIND) + + ///Defines what kind of 'organ' we're looking at. Sprites have names like 'm_firemoth_mothwings'. 'mothwings' would then be preference + var/preference = "" + ///Sprite datum we use to draw on the bodypart + var/datum/sprite_accessory/sprite_datum + ///Key of the icon states of all the sprite_datums for easy caching + var/cache_key = "" + + ///With what DNA block do we mutate in mutate_feature() ? For genetics + var/dna_block + + ///Reference to the limb we're inside of + var/obj/item/bodypart/ownerlimb + +/**mob_sprite is optional if you havent set sprite_datums for the object, and is used mostly to generate sprite_datums from a persons DNA +* For _mob_sprite we make a distinction between "Round Snout" and "round". Round Snout is the name of the sprite datum, while "round" would be part of the sprite +* I'm sorry +*/ +/obj/item/organ/external/Initialize(mapload, mob_sprite) + . = ..() + + if(mob_sprite) + set_sprite(mob_sprite) + + cache_key = generate_icon_cache() + +/obj/item/organ/external/Insert(mob/living/carbon/reciever, special, drop_if_replaced) + var/obj/item/bodypart/limb = reciever.get_bodypart(zone) + + if(!limb) + return FALSE + + limb.external_organs.Add(src) + ownerlimb = limb + + . = ..() + + limb.contents.Add(src) + + reciever.update_body_parts() + +/obj/item/organ/external/Remove(mob/living/carbon/organ_owner, special) + . = ..() + + if(ownerlimb) + ownerlimb.external_organs.Remove(src) + ownerlimb.contents.Remove(src) + ownerlimb = null + + organ_owner.update_body_parts() + +/obj/item/organ/external/transfer_to_limb(obj/item/bodypart/bodypart, mob/living/carbon/bodypart_owner) + . = ..() + + bodypart.external_organs.Add(src) + bodypart.contents.Add(src) + +///Add the overlays we need to draw on a person. Called from _bodyparts.dm +/obj/item/organ/external/proc/get_overlays(list/overlay_list, image_dir, image_layer, body_type, image_color) + if(!sprite_datum) + return + + var/gender = (body_type == FEMALE) ? "f" : "m" + var/finished_icon_state = (sprite_datum.gender_specific ? gender : "m") + "_" + preference + "_" + sprite_datum.icon_state + mutant_bodyparts_layertext(image_layer) + var/mutable_appearance/appearance = mutable_appearance(sprite_datum.icon, finished_icon_state, layer = -image_layer) + appearance.dir = image_dir + + if(sprite_datum.color_src) //There are multiple flags, but only one is ever used so meh :/ + appearance.color = image_color + + if(sprite_datum.center) + center_image(appearance, sprite_datum.dimension_x, sprite_datum.dimension_y) + + overlay_list += appearance + +/obj/item/organ/external/proc/set_sprite(sprite_name) + sprite_datum = get_sprite_datum(sprite_name) + cache_key = generate_icon_cache() + +///Generate a unique key based on our sprites. So that if we've aleady drawn these sprites, they can be found in the cache and wont have to be drawn again (blessing and curse) +/obj/item/organ/external/proc/generate_icon_cache() + return "[sprite_datum.icon_state]_[preference]" + +/**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. +*/ +/obj/item/organ/external/proc/mutant_bodyparts_layertext(layer) + switch(layer) + if(EXTERNAL_BEHIND_LAYER) + return "_BEHIND" + if(EXTERNAL_ADJACENT_LAYER) + return "_ADJ" + if(EXTERNAL_FRONT_LAYER) + return "_FRONT" + +///Converts a bitflag to the right layer. I'd love to make this a static index list, but byond made an attempt on my life when i did +/obj/item/organ/external/proc/bitflag_to_layer(layer) + switch(layer) + if(EXTERNAL_BEHIND) + return EXTERNAL_BEHIND_LAYER + if(EXTERNAL_ADJACENT) + return EXTERNAL_ADJACENT_LAYER + if(EXTERNAL_FRONT) + return EXTERNAL_FRONT_LAYER + +///Because all the preferences have names like "Beautiful Sharp Snout" we need to get the sprite datum with the actual important info +/obj/item/organ/external/proc/get_sprite_datum(sprite) + var/list/feature_list = get_global_feature_list() + return feature_list[sprite] + +///Return a dumb glob list for this specific feature (called from parse_sprite) +/obj/item/organ/external/proc/get_global_feature_list() + return null + +///Check whether we can draw the overlays. You generally don't want lizard snouts to draw over an EVA suit +/obj/item/organ/external/proc/can_draw_on_bodypart(mob/living/carbon/human/human) + return TRUE + +///Update our features after something changed our appearance +/obj/item/organ/external/proc/mutate_feature(features, mob/living/carbon/human/human) + if(!dna_block || !get_global_feature_list()) + return + + var/list/feature_list = get_global_feature_list() + + set_sprite(feature_list[deconstruct_block(getblock(features, dna_block), feature_list.len)]) + +///The horns of a lizard! +/obj/item/organ/external/horns + zone = BODY_ZONE_HEAD + slot = ORGAN_SLOT_EXTERNAL_HORNS + layers = EXTERNAL_ADJACENT + + preference = "horns" + + dna_block = DNA_HORNS_BLOCK + +/obj/item/organ/external/horns/can_draw_on_bodypart(mob/living/carbon/human/human) + if(!(human.head?.flags_inv & HIDEHAIR) || (human.wear_mask?.flags_inv & HIDEHAIR)) + return TRUE + return FALSE + +/obj/item/organ/external/horns/get_global_feature_list() + return GLOB.horns_list + +///The frills of a lizard (like weird fin ears) +/obj/item/organ/external/frills + zone = BODY_ZONE_HEAD + slot = ORGAN_SLOT_EXTERNAL_FRILLS + layers = EXTERNAL_ADJACENT + + preference = "frills" + + dna_block = DNA_FRILLS_BLOCK + +/obj/item/organ/external/frills/can_draw_on_bodypart(mob/living/carbon/human/human) + if(!(human.head?.flags_inv & HIDEEARS)) + return TRUE + return FALSE + + +/obj/item/organ/external/frills/get_global_feature_list() + return GLOB.frills_list + +///Guess what part of the lizard this is? +/obj/item/organ/external/snout + zone = BODY_ZONE_HEAD + slot = ORGAN_SLOT_EXTERNAL_SNOUT + layers = EXTERNAL_ADJACENT + + preference = "snout" + + dna_block = DNA_SNOUT_BLOCK + +/obj/item/organ/external/snout/can_draw_on_bodypart(mob/living/carbon/human/human) + if(!(human.wear_mask?.flags_inv & HIDESNOUT) && !(human.head?.flags_inv & HIDESNOUT)) + return TRUE + return FALSE + +/obj/item/organ/external/snout/get_global_feature_list() + return GLOB.snouts_list + +///A moth's antennae +/obj/item/organ/external/antennae + zone = BODY_ZONE_HEAD + slot = ORGAN_SLOT_EXTERNAL_ANTENNAE + layers = EXTERNAL_FRONT | EXTERNAL_BEHIND + + preference = "moth_antennae" + + dna_block = DNA_MOTH_ANTENNAE_BLOCK + + ///Are we burned? + var/burnt = FALSE + ///Store our old sprite here for if our antennae wings are healed + var/original_sprite = "" + +/obj/item/organ/external/antennae/Insert(mob/living/carbon/reciever, special, drop_if_replaced) + . = ..() + + RegisterSignal(reciever, COMSIG_HUMAN_BURNING, .proc/try_burn_antennae) + RegisterSignal(reciever, COMSIG_LIVING_POST_FULLY_HEAL, .proc/heal_antennae) + +/obj/item/organ/external/antennae/Remove(mob/living/carbon/organ_owner, special) + . = ..() + + UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL)) + +/obj/item/organ/external/antennae/get_global_feature_list() + return GLOB.moth_antennae_list + +/obj/item/organ/external/antennae/can_draw_on_bodypart(mob/living/carbon/human/human) + if(!(human.head?.flags_inv & HIDEHAIR) || (human.wear_mask?.flags_inv & HIDEHAIR)) + return TRUE + return FALSE + +///For moth antennae and wings we make an exception. If their features are burnt, we only update our original sprite +/obj/item/organ/external/antennae/set_sprite(sprite) + if(!burnt) + return ..() //no one listens to the return value, I just need to call the parent proc and end the code + + original_sprite = sprite + +///check if our antennae can burn off ;_; +/obj/item/organ/external/antennae/proc/try_burn_antennae(mob/living/carbon/human/human) + SIGNAL_HANDLER + + if(!burnt && human.bodytemperature >= 800 && human.fire_stacks > 0) //do not go into the extremely hot light. you will not survive + to_chat(human, span_danger("Your precious antennae burn to a crisp!")) + + burn_antennae() + human.update_body_parts() + +/obj/item/organ/external/antennae/proc/burn_antennae() + burnt = TRUE + original_sprite = sprite_datum.name + set_sprite("Burnt Off") + +///heal our antennae back up!! +/obj/item/organ/external/antennae/proc/heal_antennae() + SIGNAL_HANDLER + + if(burnt) + burnt = FALSE + set_sprite(original_sprite) diff --git a/code/modules/surgery/organs/stomach/external/wings.dm b/code/modules/surgery/organs/stomach/external/wings.dm new file mode 100644 index 00000000000..186e8937bb6 --- /dev/null +++ b/code/modules/surgery/organs/stomach/external/wings.dm @@ -0,0 +1,229 @@ +///Wing base type. doesn't really do anything +/obj/item/organ/external/wings + zone = BODY_ZONE_CHEST + slot = ORGAN_SLOT_EXTERNAL_WINGS + layers = EXTERNAL_BEHIND | EXTERNAL_ADJACENT | EXTERNAL_FRONT + + preference = "wings" + +/obj/item/organ/external/wings/can_draw_on_bodypart(mob/living/carbon/human/human) + if(!human.wear_suit) + return TRUE + if(human.wear_suit.flags_inv & ~HIDEJUMPSUIT) + return TRUE + if(human.wear_suit.species_exception && is_type_in_list(src, human.wear_suit.species_exception)) + return TRUE + return FALSE + +///The true wings that you can use to fly and shit (you cant actually shit with them, but it does wing stuff) +/obj/item/organ/external/wings/functional + ///The flight action object + var/datum/action/innate/flight/fly + + ///The preference type for opened wings + var/wings_open_preference = "wingsopen" + ///The preference type for closed wings + var/wings_closed_preference = "wings" + + ///Are our wings open or closed? + var/wings_open = FALSE + +/obj/item/organ/external/wings/functional/get_global_feature_list() + if(wings_open) + return GLOB.wings_open_list + else + return GLOB.wings_list + +/obj/item/organ/external/wings/functional/Insert(mob/living/carbon/reciever, special, drop_if_replaced) + . = ..() + + if(isnull(fly)) + fly = new + fly.Grant(reciever) + +/obj/item/organ/external/wings/functional/Remove(mob/living/carbon/organ_owner, special) + . = ..() + + fly.Remove(organ_owner) + +/obj/item/organ/external/wings/functional/on_life(delta_time, times_fired) + . = ..() + + handle_flight(owner) + +///Called on_life(). Handle flight code and check if we're still flying +/obj/item/organ/external/wings/functional/proc/handle_flight(mob/living/carbon/human/human) + if(human.movement_type & ~FLYING) + return FALSE + if(!can_fly(human)) + toggle_flight(human) + return FALSE + return TRUE + + +///Check if we're still eligible for flight (wings covered, atmosphere too thin, etc) +/obj/item/organ/external/wings/functional/proc/can_fly(mob/living/carbon/human/human) + if(human.stat || human.body_position == LYING_DOWN) + return FALSE + //Jumpsuits have tail holes, so it makes sense they have wing holes too + if(human.wear_suit && ((human.wear_suit.flags_inv & HIDEJUMPSUIT) && (!human.wear_suit.species_exception || !is_type_in_list(src, human.wear_suit.species_exception)))) + to_chat(human, span_warning("Your suit blocks your wings from extending!")) + return FALSE + var/turf/location = get_turf(human) + if(!location) + return FALSE + + var/datum/gas_mixture/environment = location.return_air() + if(environment && !(environment.return_pressure() > 30)) + to_chat(human, span_warning("The atmosphere is too thin for you to fly!")) + return FALSE + else + return TRUE + +///Slipping but in the air? +/obj/item/organ/external/wings/functional/proc/fly_slip(mob/living/carbon/human/human) + var/obj/buckled_obj + if(human.buckled) + buckled_obj = human.buckled + + to_chat(human, span_notice("Your wings spazz out and launch you!")) + + playsound(human.loc, 'sound/misc/slip.ogg', 50, TRUE, -3) + + for(var/obj/item/choking_hazard in human.held_items) + human.accident(choking_hazard) + + var/olddir = human.dir + + human.stop_pulling() + if(buckled_obj) + buckled_obj.unbuckle_mob(human) + step(buckled_obj, olddir) + else + new /datum/forced_movement(human, get_ranged_target_turf(human, olddir, 4), 1, FALSE, CALLBACK(human, /mob/living/carbon/.proc/spin, 1, 1)) + return TRUE + +///UNSAFE PROC, should only be called through the Activate or other sources that check for CanFly +/obj/item/organ/external/wings/functional/proc/toggle_flight(mob/living/carbon/human/human) + if(!HAS_TRAIT_FROM(human, TRAIT_MOVE_FLYING, SPECIES_FLIGHT_TRAIT)) + human.physiology.stun_mod *= 2 + ADD_TRAIT(human, TRAIT_NO_FLOATING_ANIM, SPECIES_FLIGHT_TRAIT) + ADD_TRAIT(human, TRAIT_MOVE_FLYING, SPECIES_FLIGHT_TRAIT) + passtable_on(human, SPECIES_TRAIT) + open_wings() + else + human.physiology.stun_mod *= 0.5 + REMOVE_TRAIT(human, TRAIT_NO_FLOATING_ANIM, SPECIES_FLIGHT_TRAIT) + REMOVE_TRAIT(human, TRAIT_MOVE_FLYING, SPECIES_FLIGHT_TRAIT) + passtable_off(human, SPECIES_TRAIT) + close_wings() + +///SPREAD OUR WINGS AND FLLLLLYYYYYY +/obj/item/organ/external/wings/functional/proc/open_wings() + preference = wings_open_preference + wings_open = TRUE + + cache_key = generate_icon_cache() //we've changed preference to open, so we only need to update the key and ask for an update to change our sprite + owner.update_body_parts() + +///close our wings +/obj/item/organ/external/wings/functional/proc/close_wings() + preference = wings_closed_preference + wings_open = FALSE + + cache_key = generate_icon_cache() + owner.update_body_parts() + if(isturf(owner?.loc)) + var/turf/location = loc + location.Entered(src, NONE) + +///hud action for starting and stopping flight +/datum/action/innate/flight + name = "Toggle Flight" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_IMMOBILE + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "flight" + +/datum/action/innate/flight/Activate() + var/mob/living/carbon/human/human = owner + var/obj/item/organ/external/wings/functional/wings = human.getorganslot(ORGAN_SLOT_EXTERNAL_WINGS) + if(wings && wings.can_fly(human)) + wings.toggle_flight(human) + if(!(human.movement_type & FLYING)) + to_chat(human, span_notice("You settle gently back onto the ground...")) + else + to_chat(human, span_notice("You beat your wings and begin to hover gently above the ground...")) + human.set_resting(FALSE, TRUE) + +///Moth wings! They can flutter in low-grav and burn off in heat +/obj/item/organ/external/wings/moth + preference = "moth_wings" + layers = EXTERNAL_BEHIND | EXTERNAL_FRONT + + dna_block = DNA_MOTH_WINGS_BLOCK + + ///Are we burned? + var/burnt = FALSE + ///Store our old sprite here for if our burned wings are healed + var/original_sprite = "" + +/obj/item/organ/external/wings/moth/get_global_feature_list() + return GLOB.moth_wings_list + +/obj/item/organ/external/wings/moth/Insert(mob/living/carbon/reciever, special, drop_if_replaced) + . = ..() + + RegisterSignal(reciever, COMSIG_HUMAN_BURNING, .proc/try_burn_wings) + RegisterSignal(reciever, COMSIG_LIVING_POST_FULLY_HEAL, .proc/heal_wings) + RegisterSignal(reciever, COMSIG_MOVABLE_PRE_MOVE, .proc/update_float_move) + +/obj/item/organ/external/wings/moth/Remove(mob/living/carbon/organ_owner, special) + . = ..() + + UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_MOVABLE_PRE_MOVE)) + REMOVE_TRAIT(organ_owner, TRAIT_FREE_FLOAT_MOVEMENT, src) + +///For moth antennae and wings we make an exception. If their features are burnt, we only update our original sprite +/obj/item/organ/external/wings/moth/set_sprite(sprite) + if(!burnt) + return ..() //no one listens to the return value, I just need to call the parent proc and end the code + + original_sprite = sprite + +///Check if we can flutter around +/obj/item/organ/external/wings/moth/proc/update_float_move() + SIGNAL_HANDLER + + if(!isspaceturf(owner.loc) && !burnt) + var/datum/gas_mixture/current = owner.loc.return_air() + if(current && (current.return_pressure() >= ONE_ATMOSPHERE*0.85)) //as long as there's reasonable pressure and no gravity, flight is possible + ADD_TRAIT(owner, TRAIT_FREE_FLOAT_MOVEMENT, src) + return + + REMOVE_TRAIT(owner, TRAIT_FREE_FLOAT_MOVEMENT, src) + +///check if our wings can burn off ;_; +/obj/item/organ/external/wings/moth/proc/try_burn_wings(mob/living/carbon/human/human) + SIGNAL_HANDLER + + if(!burnt && human.bodytemperature >= 800 && human.fire_stacks > 0) //do not go into the extremely hot light. you will not survive + to_chat(human, span_danger("Your precious wings burn to a crisp!")) + SEND_SIGNAL(human, COMSIG_ADD_MOOD_EVENT, "burnt_wings", /datum/mood_event/burnt_wings) + + burn_wings() + human.update_body_parts() + +///burn the wings off +/obj/item/organ/external/wings/moth/proc/burn_wings() + burnt = TRUE + + original_sprite = sprite_datum.name + set_sprite("Burnt Off") + +///heal our wings back up!! +/obj/item/organ/external/wings/moth/proc/heal_wings() + SIGNAL_HANDLER + + if(burnt) + burnt = FALSE + set_sprite(original_sprite) diff --git a/tgstation.dme b/tgstation.dme index b35027c7f76..3bafd0532b5 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3457,6 +3457,8 @@ #include "code\modules\surgery\organs\vocal_cords.dm" #include "code\modules\surgery\organs\stomach\_stomach.dm" #include "code\modules\surgery\organs\stomach\stomach_ethereal.dm" +#include "code\modules\surgery\organs\stomach\external\_external_organs.dm" +#include "code\modules\surgery\organs\stomach\external\wings.dm" #include "code\modules\swarmers\swarmer.dm" #include "code\modules\swarmers\swarmer_act.dm" #include "code\modules\swarmers\swarmer_objs.dm"