diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0541aa082babf..1a2e4973a9c89 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,7 +9,7 @@ /SQL/ @kannthus # Maintainers -/modular_doppler/ @Ephemeralis @CliffracerX +/modular_doppler/ @CliffracerX @Ephemeralis # Maptainers /_maps/ @SylvetteSylph @Mintybea diff --git a/.github/workflows/codeowner_reviews.yml b/.github/workflows/codeowner_reviews.yml index e6cfb98027901..54f1c0b1d39bc 100644 --- a/.github/workflows/codeowner_reviews.yml +++ b/.github/workflows/codeowner_reviews.yml @@ -7,7 +7,9 @@ on: jobs: assign-users: - + permissions: + contents: read + pull-requests: write runs-on: ubuntu-latest timeout-minutes: 5 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index b722c1a96155b..dad5afdb96979 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -152,7 +152,10 @@ ///The species is forced to have digitigrade legs in generation. #define DIGITIGRADE_FORCED 2 -///Digitigrade's prefs, used in features for legs if you're meant to be a Digitigrade. +// Preferences for leg types +/// Legs that are normal +#define NORMAL_LEGS "Normal Legs" +/// Digitgrade legs that are like bended and uhhh no shoes #define DIGITIGRADE_LEGS "Digitigrade Legs" // Health/damage defines diff --git a/code/__HELPERS/duplicating.dm b/code/__HELPERS/duplicating.dm index 225dca91fb5b1..f0f3f9a9fce97 100644 --- a/code/__HELPERS/duplicating.dm +++ b/code/__HELPERS/duplicating.dm @@ -15,8 +15,6 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars, list( "contents", "cooldowns", "_datum_components", - "external_organs", - "external_organs_slot", "group", "hand_bodyparts", "held_items", diff --git a/code/controllers/subsystem/sprite_accessories.dm b/code/controllers/subsystem/sprite_accessories.dm index 21bafd5330c97..8930976610877 100644 --- a/code/controllers/subsystem/sprite_accessories.dm +++ b/code/controllers/subsystem/sprite_accessories.dm @@ -40,7 +40,6 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity var/list/horns_list var/list/frills_list var/list/spines_list - var/list/legs_list var/list/tail_spines_list //Mutant Human bits @@ -87,7 +86,7 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity socks_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/socks)[DEFAULT_SPRITE_LIST] - lizard_markings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/lizard_markings, add_blank = TRUE)[DEFAULT_SPRITE_LIST] + lizard_markings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/lizard_markings)[DEFAULT_SPRITE_LIST] tails_list_human = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, add_blank = TRUE)[DEFAULT_SPRITE_LIST] tails_list_lizard = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard)[DEFAULT_SPRITE_LIST] tails_list_monkey = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/monkey)[DEFAULT_SPRITE_LIST] @@ -99,7 +98,6 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity frills_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, add_blank = TRUE)[DEFAULT_SPRITE_LIST] spines_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, add_blank = TRUE)[DEFAULT_SPRITE_LIST] tail_spines_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/tail_spines, add_blank = TRUE)[DEFAULT_SPRITE_LIST] - legs_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/legs)[DEFAULT_SPRITE_LIST] caps_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/caps)[DEFAULT_SPRITE_LIST] moth_wings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings)[DEFAULT_SPRITE_LIST] moth_antennae_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_antennae)[DEFAULT_SPRITE_LIST] diff --git a/code/datums/bodypart_overlays/markings_bodypart_overlay.dm b/code/datums/bodypart_overlays/markings_bodypart_overlay.dm index 5c11fe9f70334..c2c6f54d861d3 100644 --- a/code/datums/bodypart_overlays/markings_bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/markings_bodypart_overlay.dm @@ -15,7 +15,7 @@ /datum/bodypart_overlay/simple/body_marking/get_image(layer, obj/item/bodypart/limb) var/gender_string = (use_gender && limb.is_dimorphic) ? (limb.gender == MALE ? MALE : FEMALE + "_") : "" //we only got male and female sprites - return mutable_appearance(icon, gender_string + icon_state + "_" + limb.body_zone, layer = layer) + return image(icon, gender_string + icon_state + "_" + limb.body_zone, layer = layer) /datum/bodypart_overlay/simple/body_marking/moth dna_feature_key = "moth_markings" diff --git a/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm b/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm index 3115e3ad62ea2..d0250cce686eb 100644 --- a/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm @@ -13,6 +13,24 @@ ///Take on the dna/preference from whoever we're gonna be inserted in var/imprint_on_next_insertion = TRUE +/datum/bodypart_overlay/mutant/New(obj/item/organ/attached_organ) + . = ..() + + RegisterSignal(attached_organ, COMSIG_ORGAN_IMPLANTED, PROC_REF(on_mob_insert)) + +/datum/bodypart_overlay/mutant/proc/on_mob_insert(obj/item/organ/parent, mob/living/carbon/receiver) + SIGNAL_HANDLER + + if(!should_visual_organ_apply_to(parent.type, receiver)) + stack_trace("adding a [parent.type] to a [receiver.type] when it shouldn't be!") + + if(imprint_on_next_insertion) //We only want this set *once* + var/feature_name = receiver.dna.features[feature_key] + if (isnull(feature_name)) + feature_name = receiver.dna.species.mutant_organs[parent.type] + set_appearance_from_name(feature_name) + imprint_on_next_insertion = FALSE + /datum/bodypart_overlay/mutant/get_overlay(layer, obj/item/bodypart/limb) inherit_color(limb) // If draw_color is not set yet, go ahead and do that return ..() @@ -67,7 +85,6 @@ return appearance /datum/bodypart_overlay/mutant/color_image(image/overlay, layer, obj/item/bodypart/limb) - overlay.color = sprite_datum.color_src ? draw_color : null /datum/bodypart_overlay/mutant/added_to_limb(obj/item/bodypart/limb) @@ -139,3 +156,4 @@ CRASH("External organ [type] couldn't find sprite accessory [accessory_name]!") else CRASH("External organ [type] had fetch_sprite_datum called with a null accessory name!") + diff --git a/code/datums/dna.dm b/code/datums/dna.dm index df92d57c59b4c..4fff0104439c1 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -682,8 +682,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) if(dna.features["pod_hair"]) dna.features["pod_hair"] = SSaccessories.pod_hair_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_HAIR_BLOCK), length(SSaccessories.pod_hair_list))] - for(var/obj/item/organ/external/external_organ in organs) - external_organ.mutate_feature(features, src) + for(var/obj/item/organ/organ in organs) + organ.mutate_feature(features, src) if(icon_update) update_body(is_creating = mutcolor_update) diff --git a/code/datums/greyscale/_greyscale_config.dm b/code/datums/greyscale/_greyscale_config.dm index 60c12c25b9ef1..b8b428ce4222f 100644 --- a/code/datums/greyscale/_greyscale_config.dm +++ b/code/datums/greyscale/_greyscale_config.dm @@ -57,7 +57,7 @@ if(!json_config) stack_trace("Greyscale config object [DebugName()] is missing a json configuration, make sure `json_config` has been assigned a value.") string_json_config = "[json_config]" - if(findtext(string_json_config, "code/datums/greyscale/json_configs/") != 1) + if(!(findtext(string_json_config, "code/datums/greyscale/json_configs/") != 1 || findtext(string_json_config, "modular_doppler/modular_cosmetics/GAGS/json_configs") != 1)) //DOPPLER EDIT originally if(findtext(string_json_config, "code/datums/greyscale/json_configs/") != 1) stack_trace("All greyscale json configuration files should be located within 'code/datums/greyscale/json_configs/'") if(!icon_file) stack_trace("Greyscale config object [DebugName()] is missing an icon file, make sure `icon_file` has been assigned a value.") diff --git a/code/datums/quirks/neutral_quirks/transhumanist.dm b/code/datums/quirks/neutral_quirks/transhumanist.dm index ea6494a6b327b..aa8ae075df395 100644 --- a/code/datums/quirks/neutral_quirks/transhumanist.dm +++ b/code/datums/quirks/neutral_quirks/transhumanist.dm @@ -127,10 +127,10 @@ else if(isorgan(new_part)) var/obj/item/organ/new_organ = new_part old_part = human_holder.get_organ_slot(new_organ.slot) - if(new_organ.Insert(human_holder, special = TRUE)) - old_part.moveToNullspace() - STOP_PROCESSING(SSobj, old_part) - slot_string = new_organ.name + new_organ.Insert(human_holder, special = TRUE) + old_part.moveToNullspace() + STOP_PROCESSING(SSobj, old_part) + slot_string = new_organ.name /datum/quirk/transhumanist/post_add() if(!slot_string) diff --git a/code/datums/sprite_accessories.dm b/code/datums/sprite_accessories.dm index a37926405f9c0..784e9422541bd 100644 --- a/code/datums/sprite_accessories.dm +++ b/code/datums/sprite_accessories.dm @@ -34,8 +34,6 @@ * This is the source that this accessory will get its color from. Default is MUTCOLOR, but can also be HAIR, FACEHAIR, EYECOLOR and 0 if none. */ var/color_src = MUTANT_COLOR - /// Decides if this sprite has an "inner" part, such as the fleshy parts on ears. - var/hasinner = FALSE /// Is this part locked from roundstart selection? Used for parts that apply effects. var/locked = FALSE /// Should we center the sprite? @@ -1726,6 +1724,10 @@ /datum/sprite_accessory/lizard_markings icon = 'icons/mob/human/species/lizard/lizard_markings.dmi' +/datum/sprite_accessory/lizard_markings/none + name = "None" + icon_state = "none" + /datum/sprite_accessory/lizard_markings/dtiger name = "Dark Tiger Body" icon_state = "dtiger" @@ -1890,7 +1892,6 @@ /datum/sprite_accessory/ears/cat name = "Cat" icon_state = "cat" - hasinner = TRUE color_src = HAIR_COLOR /datum/sprite_accessory/ears/cat/big @@ -1917,7 +1918,6 @@ icon = 'icons/mob/human/fox_features.dmi' name = "Fox" icon_state = "fox" - hasinner = TRUE color_src = HAIR_COLOR locked = TRUE @@ -2124,16 +2124,6 @@ name = "Aquatic" icon_state = "aqua" -/datum/sprite_accessory/legs //legs are a special case, they aren't actually sprite_accessories but are updated with them. - icon = null //These datums exist for selecting legs on preference, and little else - em_block = TRUE - -/datum/sprite_accessory/legs/none - name = "Normal Legs" - -/datum/sprite_accessory/legs/digitigrade_lizard - name = DIGITIGRADE_LEGS - /datum/sprite_accessory/caps icon = 'icons/mob/human/species/mush_cap.dmi' color_src = HAIR_COLOR diff --git a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm index e3e7112b0fec9..4786bf5753c9f 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm @@ -125,7 +125,6 @@ //useless organs we throw in just to fuck with surgeons a bit more. they aren't part of a bonus, just the (absolute) state of flies /obj/item/organ/internal/fly desc = FLY_INFUSED_ORGAN_DESC - visual = FALSE /obj/item/organ/internal/fly/Initialize(mapload) . = ..() diff --git a/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm index ab33c1ad57ef3..3fecac3bb6dbf 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm @@ -6,17 +6,4 @@ visual = TRUE damage_multiplier = 2 -/obj/item/organ/internal/ears/fox/on_mob_insert(mob/living/carbon/human/ear_owner) - . = ..() - if(istype(ear_owner) && ear_owner.dna) - color = ear_owner.hair_color - ear_owner.dna.features["ears"] = ear_owner.dna.species.mutant_bodyparts["ears"] = "Fox" - ear_owner.dna.update_uf_block(DNA_EARS_BLOCK) - ear_owner.update_body() - -/obj/item/organ/internal/ears/fox/on_mob_remove(mob/living/carbon/human/ear_owner) - . = ..() - if(istype(ear_owner) && ear_owner.dna) - color = ear_owner.hair_color - ear_owner.dna.species.mutant_bodyparts -= "ears" - ear_owner.update_body() + sprite_accessory_override = /datum/sprite_accessory/ears/fox diff --git a/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm b/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm index 797c7839b2c29..9fcf7e483bba9 100644 --- a/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm @@ -34,7 +34,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... AddElement(/datum/element/noticable_organ, "%PRONOUN_They radiate%PRONOUN_s an aura of serenity.") AddElement(/datum/element/update_icon_blocker) -/obj/item/organ/internal/heart/gondola/Insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/internal/heart/gondola/mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(!(FACTION_HOSTILE in receiver.faction)) factions_to_remove += FACTION_HOSTILE @@ -42,7 +42,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... factions_to_remove += FACTION_MINING receiver.faction |= list(FACTION_HOSTILE, FACTION_MINING) -/obj/item/organ/internal/heart/gondola/Remove(mob/living/carbon/heartless, special, movement_flags) +/obj/item/organ/internal/heart/gondola/mob_remove(mob/living/carbon/heartless, special, movement_flags) . = ..() for(var/faction in factions_to_remove) heartless.faction -= faction @@ -64,11 +64,11 @@ Fluoride Stare: After someone says 5 words, blah blah blah... AddElement(/datum/element/noticable_organ, "%PRONOUN_Their mouth is permanently affixed into a relaxed smile.", BODY_ZONE_PRECISE_MOUTH) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/gondola) -/obj/item/organ/internal/tongue/gondola/Insert(mob/living/carbon/tongue_owner, special, movement_flags) +/obj/item/organ/internal/tongue/gondola/mob_insert(mob/living/carbon/tongue_owner, special, movement_flags) . = ..() tongue_owner.add_mood_event("gondola_zen", /datum/mood_event/gondola_serenity) -/obj/item/organ/internal/tongue/gondola/Remove(mob/living/carbon/tongue_owner, special, movement_flags) +/obj/item/organ/internal/tongue/gondola/mob_remove(mob/living/carbon/tongue_owner, special, movement_flags) tongue_owner.clear_mood_event("gondola_zen") return ..() @@ -87,7 +87,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... AddElement(/datum/element/noticable_organ, "%PRONOUN_Their left arm has small needles breaching the skin all over it.", BODY_ZONE_L_ARM) AddElement(/datum/element/noticable_organ, "%PRONOUN_Their right arm has small needles breaching the skin all over it.", BODY_ZONE_R_ARM) -/obj/item/organ/internal/liver/gondola/Insert(mob/living/carbon/liver_owner, special, movement_flags) +/obj/item/organ/internal/liver/gondola/mob_insert(mob/living/carbon/liver_owner, special, movement_flags) . = ..() var/has_left = liver_owner.has_left_hand(check_disabled = FALSE) var/has_right = liver_owner.has_right_hand(check_disabled = FALSE) @@ -102,7 +102,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... RegisterSignal(liver_owner, COMSIG_LIVING_TRY_PULL, PROC_REF(on_owner_try_pull)) RegisterSignal(liver_owner, COMSIG_CARBON_HELPED, PROC_REF(on_hug)) -/obj/item/organ/internal/liver/gondola/Remove(mob/living/carbon/liver_owner, special, movement_flags) +/obj/item/organ/internal/liver/gondola/mob_remove(mob/living/carbon/liver_owner, special, movement_flags) . = ..() UnregisterSignal(liver_owner, list(COMSIG_HUMAN_EQUIPPING_ITEM, COMSIG_LIVING_TRY_PULL, COMSIG_CARBON_HELPED)) diff --git a/code/game/objects/items/body_egg.dm b/code/game/objects/items/body_egg.dm index d244d8c55cc16..d8b48e0789b21 100644 --- a/code/game/objects/items/body_egg.dm +++ b/code/game/objects/items/body_egg.dm @@ -15,15 +15,14 @@ if(iscarbon(loc)) Insert(loc) -/obj/item/organ/internal/body_egg/Insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/internal/body_egg/mob_insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() - if(!.) - return + egg_owner.add_traits(list(TRAIT_XENO_HOST, TRAIT_XENO_IMMUNE), ORGAN_TRAIT) egg_owner.med_hud_set_status() INVOKE_ASYNC(src, PROC_REF(AddInfectionImages), egg_owner) -/obj/item/organ/internal/body_egg/Remove(mob/living/carbon/egg_owner, special, movement_flags) +/obj/item/organ/internal/body_egg/mob_remove(mob/living/carbon/egg_owner, special, movement_flags) . = ..() egg_owner.remove_traits(list(TRAIT_XENO_HOST, TRAIT_XENO_IMMUNE), ORGAN_TRAIT) egg_owner.med_hud_set_status() diff --git a/code/modules/admin/verbs/manipulate_organs.dm b/code/modules/admin/verbs/manipulate_organs.dm index bfb5050dafa21..6c0a86126b607 100644 --- a/code/modules/admin/verbs/manipulate_organs.dm +++ b/code/modules/admin/verbs/manipulate_organs.dm @@ -18,10 +18,7 @@ ADMIN_VERB(manipulate_organs, R_DEBUG, "Manipulate Organs", "Manipulate the orga return organ_to_grant = organs[organ_to_grant] organ_to_grant = new organ_to_grant - if(!organ_to_grant.Insert(carbon_victim)) - to_chat(user, span_notice("[carbon_victim] is unable to carry this organ!")) - qdel(organ_to_grant) - return + organ_to_grant.Insert(carbon_victim) log_admin("[key_name(user)] has added organ [organ_to_grant.type] to [key_name(carbon_victim)]") message_admins("[key_name_admin(user)] has added organ [organ_to_grant.type] to [ADMIN_LOOKUPFLW(carbon_victim)]") diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index 107d74d9c3975..d6f4c124d36c7 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -526,7 +526,7 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w var/forename = names.len > 1 ? names[2] : names[1] var/newname = "[forename]-[pick(honorifics["[H.gender]"])]" H.fully_replace_character_name(H.real_name,newname) - H.update_mutant_bodyparts() + H.update_body_parts() if(animetype == "Yes") var/seifuku = pick(typesof(/obj/item/clothing/under/costume/schoolgirl)) var/obj/item/clothing/under/costume/schoolgirl/I = new seifuku diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm index 29ea5f1e78502..f4fe0fb1875f8 100644 --- a/code/modules/antagonists/abductor/equipment/gland.dm +++ b/code/modules/antagonists/abductor/equipment/gland.dm @@ -84,7 +84,7 @@ active_mind_control = FALSE return TRUE -/obj/item/organ/internal/heart/gland/Remove(mob/living/carbon/gland_owner, special, movement_flags) +/obj/item/organ/internal/heart/gland/mob_remove(mob/living/carbon/gland_owner, special, movement_flags) . = ..() active = FALSE if(initial(uses) == 1) @@ -93,10 +93,8 @@ hud.remove_atom_from_hud(gland_owner) clear_mind_control() -/obj/item/organ/internal/heart/gland/Insert(mob/living/carbon/gland_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/internal/heart/gland/mob_insert(mob/living/carbon/gland_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() - if(!.) - return if(special != 2 && uses) // Special 2 means abductor surgery Start() diff --git a/code/modules/antagonists/changeling/headslug_eggs.dm b/code/modules/antagonists/changeling/headslug_eggs.dm index 8f861aec2ec80..75c0881c55167 100644 --- a/code/modules/antagonists/changeling/headslug_eggs.dm +++ b/code/modules/antagonists/changeling/headslug_eggs.dm @@ -11,11 +11,11 @@ /// When this egg last got removed from a body. If -1, the egg hasn't been removed from a body. var/removal_time = -1 -/obj/item/organ/internal/body_egg/changeling_egg/Insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/internal/body_egg/changeling_egg/mob_insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() hatch_time = world.time + (removal_time == -1 ? EGG_INCUBATION_TIME : (hatch_time - removal_time)) -/obj/item/organ/internal/body_egg/changeling_egg/Remove(mob/living/carbon/egg_owner, special, movement_flags) +/obj/item/organ/internal/body_egg/changeling_egg/mob_remove(mob/living/carbon/egg_owner, special, movement_flags) . = ..() removal_time = world.time diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm index a34927a6b6f34..3020c0b2530c0 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm @@ -416,20 +416,13 @@ usable_organs -= /obj/item/organ/internal/lungs/corrupt // Their lungs are already more cursed than anything I could give them var/total_implant = rand(2, 4) - var/gave_any = FALSE for (var/i in 1 to total_implant) if (!length(usable_organs)) - break + return var/organ_path = pick_n_take(usable_organs) var/obj/item/organ/internal/to_give = new organ_path - if (!to_give.Insert(sac_target)) - qdel(to_give) - else - gave_any = TRUE - - if (!gave_any) - return + to_give.Insert(sac_target) new /obj/effect/gibspawner/human/bodypartless(get_turf(sac_target)) sac_target.visible_message(span_boldwarning("Several organs force themselves out of [sac_target]!")) diff --git a/code/modules/antagonists/nightmare/nightmare_organs.dm b/code/modules/antagonists/nightmare/nightmare_organs.dm index a77aaa79b23ea..19328f20378de 100644 --- a/code/modules/antagonists/nightmare/nightmare_organs.dm +++ b/code/modules/antagonists/nightmare/nightmare_organs.dm @@ -65,9 +65,10 @@ /obj/item/organ/internal/heart/nightmare name = "heart of darkness" desc = "An alien organ that twists and writhes when exposed to light." + visual = TRUE icon_state = "demon_heart-on" base_icon_state = "demon_heart" - visual = TRUE + color = COLOR_CRAYON_BLACK decay_factor = 0 // No love is to be found in a heart so twisted. diff --git a/code/modules/antagonists/revenant/revenant_blight.dm b/code/modules/antagonists/revenant/revenant_blight.dm index dcbc9bc8181fe..13a1ff7e1d606 100644 --- a/code/modules/antagonists/revenant/revenant_blight.dm +++ b/code/modules/antagonists/revenant/revenant_blight.dm @@ -19,7 +19,6 @@ if(affected_mob) affected_mob.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#1d2953") if(affected_mob.dna && affected_mob.dna.species) - affected_mob.dna.species.handle_mutant_bodyparts(affected_mob) affected_mob.set_haircolor(null, override = TRUE) to_chat(affected_mob, span_notice("You feel better.")) ..() @@ -66,7 +65,6 @@ affected_mob.adjustStaminaLoss(22.5 * seconds_per_tick, updating_stamina = FALSE) new /obj/effect/temp_visual/revenant(affected_mob.loc) if(affected_mob.dna && affected_mob.dna.species) - affected_mob.dna.species.handle_mutant_bodyparts(affected_mob,"#1d2953") affected_mob.set_haircolor("#1d2953", override = TRUE) affected_mob.visible_message(span_warning("[affected_mob] looks terrifyingly gaunt..."), span_revennotice("You suddenly feel like your skin is wrong...")) affected_mob.add_atom_colour("#1d2953", TEMPORARY_COLOUR_PRIORITY) diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm index 1ba43cf3d8a63..2ed2405f37687 100644 --- a/code/modules/client/preferences/_preference.dm +++ b/code/modules/client/preferences/_preference.dm @@ -111,10 +111,6 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key()) /// DOES have random body on, will this already be randomized? var/randomize_by_default = TRUE - /// If the selected species has this in its /datum/species/mutant_bodyparts, - /// will show the feature as selectable. - var/relevant_mutant_bodypart = null - /// If the selected species has this in its /datum/species/body_markings, /// will show the feature as selectable. var/relevant_body_markings = null @@ -336,8 +332,7 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key()) SHOULD_NOT_SLEEP(TRUE) if ( \ - !isnull(relevant_mutant_bodypart) \ - || !isnull(relevant_inherent_trait) \ + !isnull(relevant_inherent_trait) \ || !isnull(relevant_external_organ) \ || !isnull(relevant_head_flag) \ || !isnull(relevant_body_markings) \ diff --git a/code/modules/client/preferences/species_features/felinid.dm b/code/modules/client/preferences/species_features/felinid.dm index 4c874ea7df750..be90d806323d3 100644 --- a/code/modules/client/preferences/species_features/felinid.dm +++ b/code/modules/client/preferences/species_features/felinid.dm @@ -20,7 +20,7 @@ savefile_identifier = PREFERENCE_CHARACTER category = PREFERENCE_CATEGORY_SECONDARY_FEATURES can_randomize = FALSE - relevant_mutant_bodypart = "ears" + relevant_external_organ = /obj/item/organ/internal/ears/cat /datum/preference/choiced/ears/init_possible_values() return assoc_to_keys_features(SSaccessories.ears_list) diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm index 66c107153305e..11fefc17b8b9e 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -93,13 +93,51 @@ savefile_key = "feature_lizard_legs" savefile_identifier = PREFERENCE_CHARACTER category = PREFERENCE_CATEGORY_SECONDARY_FEATURES - relevant_mutant_bodypart = "legs" /datum/preference/choiced/lizard_legs/init_possible_values() - return assoc_to_keys_features(SSaccessories.legs_list) + return list(NORMAL_LEGS, DIGITIGRADE_LEGS) /datum/preference/choiced/lizard_legs/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["legs"] = value + // Hack to update the dummy in the preference menu + // (Because digi legs are ONLY handled on species change) + if(!isdummy(target) || target.dna.species.digitigrade_customization == DIGITIGRADE_NEVER) + return + + var/list/correct_legs = target.dna.species.bodypart_overrides.Copy() & list(BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) + + if(value == DIGITIGRADE_LEGS) + correct_legs[BODY_ZONE_R_LEG] = /obj/item/bodypart/leg/right/digitigrade + correct_legs[BODY_ZONE_L_LEG] = /obj/item/bodypart/leg/left/digitigrade + + for(var/obj/item/bodypart/old_part as anything in target.bodyparts) + if(old_part.change_exempt_flags & BP_BLOCK_CHANGE_SPECIES) + continue + + var/path = correct_legs[old_part.body_zone] + if(!path) + continue + var/obj/item/bodypart/new_part = new path() + new_part.replace_limb(target, TRUE) + new_part.update_limb(is_creating = TRUE) + qdel(old_part) + +/datum/preference/choiced/lizard_legs/is_accessible(datum/preferences/preferences) + if(!..()) + return FALSE + var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species) + return initial(species_type.digitigrade_customization) == DIGITIGRADE_OPTIONAL + + +/datum/preference/choiced/lizard_legs/is_accessible(datum/preferences/preferences) + . = ..() + + if(!.) + return + + var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species) + + return initial(species_type.digitigrade_customization) & DIGITIGRADE_OPTIONAL /datum/preference/choiced/lizard_snout savefile_key = "feature_lizard_snout" @@ -121,7 +159,7 @@ savefile_key = "feature_lizard_spines" savefile_identifier = PREFERENCE_CHARACTER category = PREFERENCE_CATEGORY_SECONDARY_FEATURES - relevant_mutant_bodypart = "spines" + relevant_external_organ = /obj/item/organ/external/spines /datum/preference/choiced/lizard_spines/init_possible_values() return assoc_to_keys_features(SSaccessories.spines_list) diff --git a/code/modules/client/preferences/species_features/mushperson.dm b/code/modules/client/preferences/species_features/mushperson.dm index 45bd9c4b72620..4b624e9c02b4f 100644 --- a/code/modules/client/preferences/species_features/mushperson.dm +++ b/code/modules/client/preferences/species_features/mushperson.dm @@ -2,7 +2,7 @@ savefile_key = "feature_mushperson_cap" savefile_identifier = PREFERENCE_CHARACTER category = PREFERENCE_CATEGORY_SECONDARY_FEATURES - relevant_mutant_bodypart = "cap" + relevant_external_organ = /obj/item/organ/external/mushroom_cap /datum/preference/choiced/mushroom_cap/init_possible_values() return assoc_to_keys_features(SSaccessories.caps_list) diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 1ec229cd1cd1e..c9f4f1b3d1bd7 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -387,7 +387,7 @@ if (organ.type == target_species.get_mutant_organ_type_for_slot(organ.slot)) continue else - if ((organ.type in target_species.mutant_organs) || (organ.type in target_species.external_organs)) + if ((organ.type in target_species.mutant_organs)) continue return TRUE return FALSE diff --git a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm index ec484ebe6ebc5..4b07baaa05890 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm @@ -10,7 +10,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and desc = "This shard seems to be directly linked to some sinister entity. It might be your god! It also gives you a really horrible rash when you hold onto it for too long." items_to_create = list(/obj/item/vorpalscythe) -/obj/item/organ/internal/cyberimp/arm/shard/scythe/Insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/internal/cyberimp/arm/shard/scythe/mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(receiver.mind) ADD_TRAIT(receiver.mind, TRAIT_MORBID, ORGAN_TRAIT) diff --git a/code/modules/mining/equipment/monster_organs/monster_organ.dm b/code/modules/mining/equipment/monster_organs/monster_organ.dm index a854f113740f8..9b6330f3467c3 100644 --- a/code/modules/mining/equipment/monster_organs/monster_organ.dm +++ b/code/modules/mining/equipment/monster_organs/monster_organ.dm @@ -36,7 +36,7 @@ icon = 'icons/obj/medical/organs/mining_organs.dmi' icon_state = "hivelord_core" actions_types = list(/datum/action/cooldown/monster_core_action) - visual = FALSE + item_flags = NOBLUDGEON slot = ORGAN_SLOT_MONSTER_CORE organ_flags = ORGAN_ORGANIC @@ -66,10 +66,9 @@ deltimer(decay_timer) return ..() -/obj/item/organ/internal/monster_core/Insert(mob/living/carbon/target_carbon, special = FALSE, movement_flags) +/obj/item/organ/internal/monster_core/mob_insert(mob/living/carbon/target_carbon, special = FALSE, movement_flags) . = ..() - if(!.) - return + if (inert) to_chat(target_carbon, span_notice("[src] breaks down as you try to insert it.")) qdel(src) @@ -80,7 +79,7 @@ target_carbon.visible_message(span_notice("[src] stabilizes as it's inserted.")) return TRUE -/obj/item/organ/internal/monster_core/Remove(mob/living/carbon/target_carbon, special, movement_flags) +/obj/item/organ/internal/monster_core/mob_remove(mob/living/carbon/target_carbon, special, movement_flags) if (!inert && !special) owner.visible_message(span_notice("[src] rapidly decays as it's removed.")) go_inert() diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index ac3ee0b9b2774..7670c4e312844 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -557,7 +557,6 @@ var/obj/item/organ/external/wings/functional/wings = get_wing_choice(exposed_human, chest) wings = new wings() wings.Insert(exposed_human) - exposed_human.dna.species.handle_mutant_bodyparts(exposed_human) playsound(exposed_human.loc, 'sound/items/poster_ripped.ogg', 50, TRUE, -1) exposed_human.apply_damage(20, def_zone = BODY_ZONE_CHEST, forced = TRUE, wound_bonus = CANT_WOUND) exposed_human.emote("scream") diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index 9303cd2347413..3c063aec7e752 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -1,6 +1,5 @@ /obj/item/organ/internal/alien icon_state = "acid" - visual = FALSE food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/toxin/acid = 10) /obj/item/organ/internal/alien/plasmavessel @@ -222,11 +221,11 @@ stomach_contents -= source UnregisterSignal(source, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_DEATH, COMSIG_QDELETING)) -/obj/item/organ/internal/stomach/alien/Insert(mob/living/carbon/stomach_owner, special, movement_flags) +/obj/item/organ/internal/stomach/alien/mob_insert(mob/living/carbon/stomach_owner, special, movement_flags) RegisterSignal(stomach_owner, COMSIG_ATOM_RELAYMOVE, PROC_REF(something_moved)) return ..() -/obj/item/organ/internal/stomach/alien/Remove(mob/living/carbon/stomach_owner, special, movement_flags) +/obj/item/organ/internal/stomach/alien/mob_remove(mob/living/carbon/stomach_owner, special, movement_flags) UnregisterSignal(stomach_owner, COMSIG_ATOM_RELAYMOVE) return ..() diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 4c98419ef412c..2350788e81f6d 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -53,13 +53,8 @@ overlays_standing[cache_index] = null SEND_SIGNAL(src, COMSIG_CARBON_REMOVE_OVERLAY, cache_index, I) -//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() - /mob/living/carbon/update_body(is_creating = FALSE) - dna?.species.handle_body(src) //This calls `handle_mutant_bodyparts` which calls `update_mutant_bodyparts()`. Don't double call! + dna?.species.handle_body(src) update_body_parts(is_creating) /mob/living/carbon/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 5e1bdf4282686..661c5fc7abbcd 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -60,16 +60,6 @@ GLOBAL_LIST_EMPTY(features_by_species) /// Use a [language holder datum][/datum/language_holder] typepath in this var. /// Should never be null. var/datum/language_holder/species_language_holder = /datum/language_holder/human_basic - /** - * 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. - * Assoc values are defaults for given bodyparts, also modified by aforementioned organs. - * They also allow for faster '[]' list access versus 'in'. Other than that, they are useless right now. - * Layer hiding is handled by [/datum/species/proc/handle_mutant_bodyparts] below. - */ - var/list/mutant_bodyparts = list() ///The bodyparts this species uses. assoc of bodypart string - bodypart type. Make sure all the fucking entries are in or I'll skin you alive. var/list/bodypart_overrides = list( BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left, @@ -79,10 +69,8 @@ GLOBAL_LIST_EMPTY(features_by_species) BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right, BODY_ZONE_CHEST = /obj/item/bodypart/chest, ) - ///Internal organs that are unique to this race, like a tail. list(typepath of organ 1, typepath of organ 2) + ///Internal organs that are unique to this race, like a tail or other cosmetic organs. list(typepath of organ 1, typepath of organ 2 = "Round"). var/list/mutant_organs = list() - ///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() ///Replaces default brain with a different organ var/obj/item/organ/internal/brain/mutantbrain = /obj/item/organ/internal/brain ///Replaces default heart with a different organ @@ -283,7 +271,10 @@ GLOBAL_LIST_EMPTY(features_by_species) if(ORGAN_SLOT_STOMACH) return mutantstomach else - CRASH("Invalid organ slot [slot]") + // Non-standard organs we might have + for(var/obj/item/organ/extra_organ as anything in mutant_organs) + if(initial(extra_organ.slot) == slot) + return extra_organ /** * Corrects organs in a carbon, removing ones it doesn't need and adding ones it does. @@ -299,46 +290,33 @@ GLOBAL_LIST_EMPTY(features_by_species) * * visual_only - boolean, only load organs that change how the species looks. Do not use for normal gameplay stuff */ /datum/species/proc/regenerate_organs(mob/living/carbon/organ_holder, datum/species/old_species, replace_current = TRUE, list/excluded_zones, visual_only = FALSE) - //what should be put in if there is no mutantorgan (brains handled separately) - var/list/organ_slots = list( - ORGAN_SLOT_BRAIN, - ORGAN_SLOT_HEART, - ORGAN_SLOT_LUNGS, - ORGAN_SLOT_APPENDIX, - ORGAN_SLOT_EYES, - ORGAN_SLOT_EARS, - ORGAN_SLOT_TONGUE, - ORGAN_SLOT_LIVER, - ORGAN_SLOT_STOMACH, - ) - - for(var/slot in organ_slots) + for(var/slot in get_all_slots()) var/obj/item/organ/existing_organ = organ_holder.get_organ_slot(slot) var/obj/item/organ/new_organ = get_mutant_organ_type_for_slot(slot) + var/old_organ_type = old_species?.get_mutant_organ_type_for_slot(slot) - if(isnull(new_organ)) // if they aren't suppose to have an organ here, remove it - if(existing_organ) - existing_organ.Remove(organ_holder, special = TRUE) + // if we have an extra organ that before changing that the species didnt have, remove it + if(!new_organ) + if(existing_organ && (old_organ_type == existing_organ.type || replace_current)) + existing_organ.Remove(organ_holder) qdel(existing_organ) continue - // we don't want to remove organs that are not the default for this species - if(!isnull(existing_organ)) - if(!isnull(old_species) && existing_organ.type != old_species.get_mutant_organ_type_for_slot(slot)) - continue - else if(!replace_current && existing_organ.type != get_mutant_organ_type_for_slot(slot)) + if(existing_organ) + // we dont want to remove organs that were not from the old species (such as from freak surgery or prosthetics) + if(existing_organ.type != old_organ_type && !replace_current) continue - // at this point we already know new_organ is not null - if(existing_organ?.type == new_organ) - continue // we don't want to remove organs that are the same as the new one + // we don't want to remove organs that are the same as the new one + if(existing_organ.type == new_organ) + continue - if(visual_only && !initial(new_organ.visual)) + if(visual_only && (!initial(new_organ.bodypart_overlay) && !initial(new_organ.visual))) continue var/used_neworgan = FALSE new_organ = SSwardrobe.provide_type(new_organ) - var/should_have = new_organ.get_availability(src, organ_holder) + var/should_have = new_organ.get_availability(src, organ_holder) && should_visual_organ_apply_to(new_organ, organ_holder) // Check for an existing organ, and if there is one check to see if we should remove it var/health_pct = 1 @@ -362,46 +340,6 @@ GLOBAL_LIST_EMPTY(features_by_species) if(!used_neworgan) QDEL_NULL(new_organ) - if(!isnull(old_species)) - for(var/mutant_organ in old_species.mutant_organs) - if(mutant_organ in mutant_organs) - continue // need this mutant organ, but we already have it! - - var/obj/item/organ/current_organ = organ_holder.get_organ_by_type(mutant_organ) - if(current_organ) - current_organ.Remove(organ_holder) - QDEL_NULL(current_organ) - - for(var/obj/item/organ/external/external_organ in organ_holder.organs) - // External organ checking. We need to check the external organs owned by the carbon itself, - // because we want to also remove ones not shared by its species. - // This should be done even if species was not changed. - if(external_organ in external_organs) - continue // Don't remove external organs this species is supposed to have. - - external_organ.Remove(organ_holder) - QDEL_NULL(external_organ) - - var/list/species_organs = mutant_organs + external_organs - for(var/organ_path in species_organs) - var/obj/item/organ/current_organ = organ_holder.get_organ_by_type(organ_path) - if(ispath(organ_path, /obj/item/organ/external) && !should_external_organ_apply_to(organ_path, organ_holder)) - if(!isnull(current_organ) && replace_current) - // if we have an organ here and we're replacing organs, remove it - current_organ.Remove(organ_holder) - QDEL_NULL(current_organ) - continue - - if(!current_organ || replace_current) - var/obj/item/organ/replacement = SSwardrobe.provide_type(organ_path) - // If there's an existing mutant organ, we're technically replacing it. - // Let's abuse the snowflake proc that skillchips added. Basically retains - // feature parity with every other organ too. - if(current_organ) - current_organ.before_organ_replacement(replacement) - // organ.Insert will qdel any current organs in that slot, so we don't need to. - replacement.Insert(organ_holder, special=TRUE, movement_flags = DELETE_IF_REPLACED) - /datum/species/proc/worn_items_fit_body_check(mob/living/carbon/wearer) for(var/obj/item/equipped_item in wearer.get_equipped_items(INCLUDE_POCKETS)) var/equipped_item_slot = wearer.get_slot_by_item(equipped_item) @@ -443,7 +381,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(old_species.type != type) replace_body(human_who_gained_species, src) - regenerate_organs(human_who_gained_species, old_species, visual_only = human_who_gained_species.visual_only_organs) + regenerate_organs(human_who_gained_species, old_species, replace_current = FALSE, visual_only = human_who_gained_species.visual_only_organs) // Drop the items the new species can't wear INVOKE_ASYNC(src, PROC_REF(worn_items_fit_body_check), human_who_gained_species, TRUE) @@ -459,16 +397,6 @@ GLOBAL_LIST_EMPTY(features_by_species) //Resets blood if it is excessively high so they don't gib normalize_blood(human_who_gained_species) - if(ishuman(human_who_gained_species)) - var/mob/living/carbon/human/human = human_who_gained_species - for(var/obj/item/organ/external/organ_path as anything in external_organs) - if(!should_external_organ_apply_to(organ_path, human)) - continue - - //Load a persons preferences from DNA - var/obj/item/organ/external/new_organ = SSwardrobe.provide_type(organ_path) - new_organ.Insert(human, special=TRUE, movement_flags = DELETE_IF_REPLACED) - add_body_markings(human_who_gained_species) if(length(inherent_traits)) @@ -509,10 +437,6 @@ GLOBAL_LIST_EMPTY(features_by_species) for(var/X in inherent_traits) REMOVE_TRAIT(C, X, SPECIES_TRAIT) - for(var/obj/item/organ/external/organ in C.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)) C.dna.remove_mutation(inert_mutation) @@ -546,14 +470,13 @@ GLOBAL_LIST_EMPTY(features_by_species) * Handles the body of a human * * Handles lipstick, having no eyes, eye color, undergarnments like underwear, undershirts, and socks, and body layers. - * Calls [handle_mutant_bodyparts][/datum/species/proc/handle_mutant_bodyparts] * Arguments: * * species_human - Human, whoever we're handling the body for */ /datum/species/proc/handle_body(mob/living/carbon/human/species_human) species_human.remove_overlay(BODY_LAYER) if(HAS_TRAIT(species_human, TRAIT_INVISIBLE_MAN)) - return handle_mutant_bodyparts(species_human) + return var/list/standing = list() if(!HAS_TRAIT(species_human, TRAIT_HUSK)) @@ -598,103 +521,7 @@ GLOBAL_LIST_EMPTY(features_by_species) species_human.overlays_standing[BODY_LAYER] = standing species_human.apply_overlay(BODY_LAYER) - handle_mutant_bodyparts(species_human) - -/** - * Handles the mutant bodyparts of a human - * - * Handles the adding and displaying of, layers, colors, and overlays of mutant bodyparts and accessories. - * Handles digitigrade leg displaying and squishing. - * Arguments: - * * H - Human, whoever we're handling the body for - * * forced_colour - The forced color of an accessory. Leave null to use mutant color. - */ -/datum/species/proc/handle_mutant_bodyparts(mob/living/carbon/human/source, 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() - - source.remove_overlay(BODY_BEHIND_LAYER) - source.remove_overlay(BODY_ADJ_LAYER) - source.remove_overlay(BODY_FRONT_LAYER) - - if(!mutant_bodyparts || HAS_TRAIT(source, TRAIT_INVISIBLE_MAN)) - return - - var/obj/item/bodypart/head/noggin = source.get_bodypart(BODY_ZONE_HEAD) - - - 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 || IS_ROBOTIC_LIMB(noggin)) - bodyparts_to_add -= "ears" - - if(!bodyparts_to_add) - return - - var/g = (source.physique == 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/accessory - switch(bodypart) - if("ears") - accessory = SSaccessories.ears_list[source.dna.features["ears"]] - if("legs") - accessory = SSaccessories.legs_list[source.dna.features["legs"]] - - if(!accessory || accessory.icon_state == "none") - continue - - var/mutable_appearance/accessory_overlay = mutable_appearance(accessory.icon, layer = -layer) - - if(accessory.gender_specific) - accessory_overlay.icon_state = "[g]_[bodypart]_[accessory.icon_state]_[layertext]" - else - accessory_overlay.icon_state = "m_[bodypart]_[accessory.icon_state]_[layertext]" - - if(accessory.em_block) - accessory_overlay.overlays += emissive_blocker(accessory_overlay.icon, accessory_overlay.icon_state, source, accessory_overlay.alpha) - - if(accessory.center) - accessory_overlay = center_image(accessory_overlay, accessory.dimension_x, accessory.dimension_y) - - if(!(HAS_TRAIT(source, TRAIT_HUSK))) - if(!forced_colour) - switch(accessory.color_src) - if(MUTANT_COLOR) - accessory_overlay.color = fixed_mut_color || source.dna.features["mcolor"] - if(HAIR_COLOR) - accessory_overlay.color = get_fixed_hair_color(source) || source.hair_color - if(FACIAL_HAIR_COLOR) - accessory_overlay.color = get_fixed_hair_color(source) || source.facial_hair_color - if(EYE_COLOR) - accessory_overlay.color = source.eye_color_left - else - accessory_overlay.color = forced_colour - standing += accessory_overlay - - if(accessory.hasinner) - var/mutable_appearance/inner_accessory_overlay = mutable_appearance(accessory.icon, layer = -layer) - if(accessory.gender_specific) - inner_accessory_overlay.icon_state = "[g]_[bodypart]inner_[accessory.icon_state]_[layertext]" - else - inner_accessory_overlay.icon_state = "m_[bodypart]inner_[accessory.icon_state]_[layertext]" - - if(accessory.center) - inner_accessory_overlay = center_image(inner_accessory_overlay, accessory.dimension_x, accessory.dimension_y) - - standing += inner_accessory_overlay - - source.overlays_standing[layer] = standing.Copy() - standing = list() - - source.apply_overlay(BODY_BEHIND_LAYER) - source.apply_overlay(BODY_ADJ_LAYER) - source.apply_overlay(BODY_FRONT_LAYER) - - update_body_markings(source) + update_body_markings(species_human) //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. @@ -737,7 +564,9 @@ GLOBAL_LIST_EMPTY(features_by_species) var/list/new_features = list() var/static/list/organs_to_randomize = list() - for(var/obj/item/organ/external/organ_path as anything in external_organs) + for(var/obj/item/organ/organ_path as anything in mutant_organs) + if(!organ_path.bodypart_overlay) + continue var/overlay_path = initial(organ_path.bodypart_overlay) var/datum/bodypart_overlay/mutant/sample_overlay = organs_to_randomize[overlay_path] if(isnull(sample_overlay)) @@ -1484,15 +1313,14 @@ GLOBAL_LIST_EMPTY(features_by_species) var/datum/preference/preference = GLOB.preference_entries[preference_type] if ( \ - (preference.relevant_mutant_bodypart in mutant_bodyparts) \ - || (preference.relevant_inherent_trait in inherent_traits) \ - || (preference.relevant_external_organ in external_organs) \ + (preference.relevant_inherent_trait in inherent_traits) \ + || (preference.relevant_external_organ in mutant_organs) \ || (preference.relevant_head_flag && check_head_flags(preference.relevant_head_flag)) \ || (preference.relevant_body_markings in body_markings) \ ) features += preference.savefile_key - for (var/obj/item/organ/external/organ_type as anything in external_organs) + for (var/obj/item/organ/organ_type as anything in mutant_organs) var/preference = initial(organ_type.preference) if (!isnull(preference)) features += preference @@ -1537,7 +1365,7 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/species/proc/get_types_to_preload() var/list/to_store = list() to_store += mutant_organs - for(var/obj/item/organ/external/horny as anything in external_organs) + for(var/obj/item/organ/horny as anything in mutant_organs) to_store += horny //Haha get it? //Don't preload brains, cause reuse becomes a horrible headache @@ -2123,6 +1951,10 @@ GLOBAL_LIST_EMPTY(features_by_species) /// Update the overlays if necessary /datum/species/proc/update_body_markings(mob/living/carbon/human/hooman) + if(HAS_TRAIT(hooman, TRAIT_INVISIBLE_MAN)) + remove_body_markings(hooman) + return + var/needs_update = FALSE for(var/datum/bodypart_overlay/simple/body_marking/marking as anything in body_markings) if(initial(marking.dna_feature_key) == body_markings[marking]) // dna is same as our species (sort of mini-cache), so no update needed 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 fe5817eab2780..e9553cc2e3efc 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -133,9 +133,9 @@ There are several things that need to be remembered: var/obj/item/bodypart/chest/my_chest = get_bodypart(BODY_ZONE_CHEST) my_chest?.worn_uniform_offset?.apply_offset(uniform_overlay) overlays_standing[UNIFORM_LAYER] = uniform_overlay - apply_overlay(UNIFORM_LAYER) - update_mutant_bodyparts() + update_body_parts() + apply_overlay(UNIFORM_LAYER) /mob/living/carbon/human/update_worn_id(update_obscured = TRUE) remove_overlay(ID_LAYER) @@ -434,12 +434,10 @@ There are several things that need to be remembered: var/obj/item/bodypart/chest/my_chest = get_bodypart(BODY_ZONE_CHEST) my_chest?.worn_suit_offset?.apply_offset(suit_overlay) overlays_standing[SUIT_LAYER] = suit_overlay - update_body_parts() - update_mutant_bodyparts() + update_body_parts() apply_overlay(SUIT_LAYER) - /mob/living/carbon/human/update_pockets() if(client && hud_used) var/atom/movable/screen/inventory/inv @@ -489,7 +487,7 @@ There are several things that need to be remembered: overlays_standing[FACEMASK_LAYER] = mask_overlay apply_overlay(FACEMASK_LAYER) - update_mutant_bodyparts() //e.g. upgate needed because mask now hides lizard snout + update_body_parts() //e.g. upgate needed because mask now hides lizard snout /mob/living/carbon/human/update_worn_back(update_obscured = TRUE) remove_overlay(BACK_LAYER) diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm index 1d7c328f88232..d4dea2abfcc6a 100644 --- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm +++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm @@ -19,7 +19,6 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest, ) inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID - mutant_bodyparts = list("wings" = "None") mutantbrain = /obj/item/organ/internal/brain/dullahan mutanteyes = /obj/item/organ/internal/eyes/dullahan mutanttongue = /obj/item/organ/internal/tongue/dullahan diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index b26bd476b4bc4..8a040425a9747 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -3,11 +3,10 @@ name = "Felinid" id = SPECIES_FELINE examine_limb_id = SPECIES_HUMAN - mutant_bodyparts = list("ears" = "Cat", "wings" = "None") mutantbrain = /obj/item/organ/internal/brain/felinid mutanttongue = /obj/item/organ/internal/tongue/cat mutantears = /obj/item/organ/internal/ears/cat - external_organs = list( + mutant_organs = list( /obj/item/organ/external/tail/cat = "Cat", ) inherent_traits = list( @@ -151,7 +150,7 @@ // stored_feature_id is only set once (the first time an organ is inserted), so this should be safe. var/obj/item/organ/internal/ears/cat/kitty_ears = new kitty_ears.Insert(soon_to_be_felinid, special = TRUE, movement_flags = DELETE_IF_REPLACED) - if(should_external_organ_apply_to(/obj/item/organ/external/tail/cat, soon_to_be_felinid)) //only give them a tail if they actually have sprites for it / are a compatible subspecies. + if(should_visual_organ_apply_to(/obj/item/organ/external/tail/cat, soon_to_be_felinid)) //only give them a tail if they actually have sprites for it / are a compatible subspecies. var/obj/item/organ/external/tail/cat/kitty_tail = new kitty_tail.Insert(soon_to_be_felinid, special = TRUE, movement_flags = DELETE_IF_REPLACED) @@ -174,8 +173,8 @@ old_tail.Remove(purrbated_human, special = TRUE) qdel(old_tail) // Locate does not work on assoc lists, so we do it by hand - for(var/external_organ in target_species.external_organs) - if(!should_external_organ_apply_to(external_organ, purrbated_human)) + for(var/external_organ in target_species.mutant_organs) + if(!should_visual_organ_apply_to(external_organ, purrbated_human)) continue if(ispath(external_organ, /obj/item/organ/external/tail)) var/obj/item/organ/external/tail/new_tail = new external_organ() diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm index ef8140c7d82ef..be6357f6b4f21 100644 --- a/code/modules/mob/living/carbon/human/species_types/humans.dm +++ b/code/modules/mob/living/carbon/human/species_types/humans.dm @@ -4,7 +4,6 @@ inherent_traits = list( TRAIT_USES_SKINTONES, ) - mutant_bodyparts = list("wings" = "None") skinned_type = /obj/item/stack/sheet/animalhide/human changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT payday_modifier = 1.1 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 6b71a234d9b61..9370db5f5a8f8 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -8,9 +8,8 @@ TRAIT_TACKLING_TAILED_DEFENDER, ) inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_REPTILE - mutant_bodyparts = list("legs" = "Normal Legs") body_markings = list(/datum/bodypart_overlay/simple/body_marking/lizard = "None") - external_organs = list( + mutant_organs = list( /obj/item/organ/external/horns = "None", /obj/item/organ/external/frills = "None", /obj/item/organ/external/snout = "Round", diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm index 449f2d775a301..2c205c150c08a 100644 --- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm +++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -3,7 +3,7 @@ /datum/species/monkey name = "\improper Monkey" id = SPECIES_MONKEY - external_organs = list( + mutant_organs = list( /obj/item/organ/external/tail/monkey = "Monkey", ) mutanttongue = /obj/item/organ/internal/tongue/monkey 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 0caa0d996ba1b..0ebfb6a5f93ff 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -8,7 +8,7 @@ ) inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG body_markings = list(/datum/bodypart_overlay/simple/body_marking/moth = "None") - external_organs = list(/obj/item/organ/external/wings/moth = "Plain", /obj/item/organ/external/antennae = "Plain") + mutant_organs = list(/obj/item/organ/external/wings/moth = "Plain", /obj/item/organ/external/antennae = "Plain") meat = /obj/item/food/meat/slab/human/mutant/moth mutanttongue = /obj/item/organ/internal/tongue/moth mutanteyes = /obj/item/organ/internal/eyes/moth @@ -28,12 +28,6 @@ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/moth, ) -/datum/species/moth/regenerate_organs(mob/living/carbon/C, datum/species/old_species, replace_current= TRUE, list/excluded_zones, visual_only) - . = ..() - if(ishuman(C)) - var/mob/living/carbon/human/H = C - handle_mutant_bodyparts(H) - /datum/species/moth/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) . = ..() RegisterSignal(human_who_gained_species, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(damage_weakness)) diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm index 14d6c1437f0da..1e2b73616d91c 100644 --- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm @@ -6,7 +6,7 @@ fixed_mut_color = "#DBBF92" - external_organs = list(/obj/item/organ/external/mushroom_cap = "Round") + mutant_organs = list(/obj/item/organ/external/mushroom_cap = "Round") inherent_traits = list( TRAIT_MUTANT_COLORS, diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index bcc0b6c4b677c..e5e735b31e44f 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -7,7 +7,7 @@ TRAIT_MUTANT_COLORS, TRAIT_PLANT_SAFE, ) - external_organs = list( + mutant_organs = list( /obj/item/organ/external/pod_hair = "None", ) inherent_biotypes = MOB_ORGANIC | MOB_HUMANOID | MOB_PLANT diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 111b35cb7f7bf..d3dfb81352004 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -17,7 +17,6 @@ TRAIT_NO_MIRROR_REFLECTION, ) inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID - mutant_bodyparts = list("wings" = "None") changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN exotic_bloodtype = "U" blood_deficiency_drain_rate = BLOOD_DEFICIENCY_MODIFIER // vampires already passively lose blood, so this just makes them lose it slightly more quickly when they have blood deficiency. diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index 3337a15b441c5..4af39c20d148a 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -32,6 +32,8 @@ var/skin_tone /// Weakref to the mob this spawner created - just if you needed to do something with it. var/datum/weakref/spawned_mob_ref + /// DOPPLER SHIFT ADDITION: allowing players to have their current character loaded + var/allow_prefs = TRUE /obj/effect/mob_spawn/Initialize(mapload) . = ..() @@ -93,7 +95,20 @@ if(!ispath(outfit_override[outfit_var]) && !isnull(outfit_override[outfit_var])) CRASH("outfit_override var on [mob_name] spawner has incorrect values! it must be an assoc list with outfit \"var\" = path | null") outfit.vars[outfit_var] = outfit_override[outfit_var] - spawned_human.equipOutfit(outfit) + //spawned_human.equipOutfit(outfit) /// DOPPLER SHIFT REMOVAL + /// DOPPLER SHIFT ADDITION BEGIN + if(allow_prefs && spawned_human.client) + spawned_human.client?.prefs.safe_transfer_prefs_to(spawned_human) + SSquirks.AssignQuirks(spawned_human, spawned_human.client) + spawned_human.equip_outfit_and_loadout(outfit, spawned_human.client?.prefs) + else + spawned_human.equipOutfit(outfit) + else if(allow_prefs && spawned_mob.client) + var/mob/living/carbon/human/spawned_human = spawned_mob + spawned_human.client?.prefs.safe_transfer_prefs_to(spawned_human) + SSquirks.AssignQuirks(spawned_human, spawned_human.client) + spawned_human.equip_outfit_and_loadout(new /datum/outfit(), spawned_human.client?.prefs) + /// DOPPLER SHIFT ADDITION END ///these mob spawn subtypes do not trigger until attacked by a ghost. /obj/effect/mob_spawn/ghost_role @@ -157,9 +172,14 @@ LAZYADD(ckeys_trying_to_spawn, user_ckey) if(prompt_ghost) - var/prompt = "Become [prompt_name]?" + var/realname = user.client?.prefs?.read_preference(/datum/preference/name/real_name) || "yourself" /// DOPPLER SHIFT ADDITION + var/prompt = "Become [prompt_name] as [realname]?" /// DOPPLER SHIFT EDIT: adding your name to it if(!temp_body && user.can_reenter_corpse && user.mind) prompt += " (Warning, You can no longer be revived!)" + /// DOPPLER SHIFT ADDITION BEGIN + if(allow_prefs) + prompt += "\nYou will be loaded in with your current character, [realname] - loadout & quirks included! Make sure they fit the role!" + /// DOPPLER SHIFT ADDITION END var/ghost_role = tgui_alert(usr, prompt, buttons = list("Yes", "No"), timeout = 10 SECONDS) if(ghost_role != "Yes" || !loc || QDELETED(user)) LAZYREMOVE(ckeys_trying_to_spawn, user_ckey) diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm index 7a802cf0f6cda..8a1d31a92f6b0 100644 --- a/code/modules/mod/modules/modules_medical.dm +++ b/code/modules/mod/modules/modules_medical.dm @@ -225,8 +225,7 @@ organ_evacced.Remove(target, special = TRUE) organ_evacced.forceMove(get_turf(target)) - if (!organ.Insert(target)) - organ.forceMove(drop_location()) + organ.Insert(target) organ = null ///Patrient Transport - Generates hardlight bags you can put people in. diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 9cec7dcca8984..7a7809ef48cba 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -953,7 +953,7 @@ if(should_draw_greyscale) //Should the limb be colored? draw_color ||= species_color || (skin_tone ? skintone2hex(skin_tone) : null) - recolor_external_organs() + recolor_bodypart_overlays() return TRUE //to update the bodypart's icon when not attached to a mob @@ -1274,7 +1274,7 @@ QDEL_NULL(current_gauze) ///Loops through all of the bodypart's external organs and update's their color. -/obj/item/bodypart/proc/recolor_external_organs() +/obj/item/bodypart/proc/recolor_bodypart_overlays() for(var/datum/bodypart_overlay/mutant/overlay in bodypart_overlays) overlay.inherit_color(src, force = TRUE) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 517847fb24f3d..c656daeb18445 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -404,12 +404,12 @@ qdel(phantom_loss) //Copied from /datum/species/proc/on_species_gain() - for(var/obj/item/organ/external/organ_path as anything in dna.species.external_organs) + for(var/obj/item/organ/organ_path as anything in dna.species.mutant_organs) //Load a persons preferences from DNA var/zone = initial(organ_path.zone) if(zone != limb_zone) continue - var/obj/item/organ/external/new_organ = SSwardrobe.provide_type(organ_path) + var/obj/item/organ/new_organ = SSwardrobe.provide_type(organ_path) new_organ.Insert(src) update_body_parts() diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index 8833f87e28ee8..dec8efb154eae 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -182,8 +182,8 @@ /mob/living/carbon/proc/synchronize_bodytypes() var/all_limb_flags = NONE for(var/obj/item/bodypart/limb as anything in bodyparts) - for(var/obj/item/organ/external/ext_organ in limb) - all_limb_flags |= ext_organ.external_bodytypes + for(var/obj/item/organ/organ in limb) + all_limb_flags |= organ.external_bodytypes all_limb_flags |= limb.bodytype bodytype = all_limb_flags @@ -192,8 +192,8 @@ /mob/living/carbon/proc/synchronize_bodyshapes() var/all_limb_flags = NONE for(var/obj/item/bodypart/limb as anything in bodyparts) - for(var/obj/item/organ/external/ext_organ in limb) - all_limb_flags |= ext_organ.external_bodyshapes + for(var/obj/item/organ/organ in limb) + all_limb_flags |= organ.external_bodyshapes all_limb_flags |= limb.bodyshape bodyshape = all_limb_flags diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm index d96d558863b12..83474363c6cf2 100644 --- a/code/modules/surgery/organ_manipulation.dm +++ b/code/modules/surgery/organ_manipulation.dm @@ -239,22 +239,20 @@ tool = tool.contents[1] target_organ = tool user.temporarilyRemoveItemFromInventory(target_organ, TRUE) - if(target_organ.Insert(target)) - if(apparatus) - apparatus.icon_state = initial(apparatus.icon_state) - apparatus.desc = initial(apparatus.desc) - apparatus.cut_overlays() - display_results( - user, - target, - span_notice("You insert [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] inserts [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] inserts something into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - display_pain(target, "Your [target.parse_zone_with_bodypart(target_zone)] throbs with pain as your new [tool.name] comes to life!") - target_organ.on_surgical_insertion(user, target, target_zone, tool) - else - target_organ.forceMove(target.loc) + target_organ.Insert(target) + if(apparatus) + apparatus.icon_state = initial(apparatus.icon_state) + apparatus.desc = initial(apparatus.desc) + apparatus.cut_overlays() + display_results( + user, + target, + span_notice("You insert [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]."), + span_notice("[user] inserts [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), + span_notice("[user] inserts something into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), + ) + display_pain(target, "Your [target.parse_zone_with_bodypart(target_zone)] throbs with pain as your new [tool.name] comes to life!") + target_organ.on_surgical_insertion(user, target, target_zone, tool) else if(current_type == "extract") if(target_organ && target_organ.owner == target) diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 98d947793efae..4d28b987abcaf 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -1,4 +1,3 @@ - /obj/item/organ name = "organ" icon = 'icons/obj/medical/organs/organs.dmi' @@ -79,6 +78,9 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) volume = reagent_vol,\ after_eat = CALLBACK(src, PROC_REF(OnEatFrom))) + if(bodypart_overlay) + setup_bodypart_overlay() + /obj/item/organ/Destroy() if(bodypart_owner && !owner && !QDELETED(bodypart_owner)) bodypart_remove(bodypart_owner) @@ -140,7 +142,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) return /obj/item/organ/proc/on_life(seconds_per_tick, times_fired) - CRASH("Oh god oh fuck something is calling parent organ life") + return /obj/item/organ/examine(mob/user) . = ..() @@ -342,3 +344,16 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /// Tries to replace the existing organ on the passed mob with this one, with special handling for replacing a brain without ghosting target /obj/item/organ/proc/replace_into(mob/living/carbon/new_owner) return Insert(new_owner, special = TRUE, movement_flags = DELETE_IF_REPLACED) + + +/// Get all possible organ slots by checking every organ, and then store it and give it whenever needed +/proc/get_all_slots() + var/static/list/all_organ_slots = list() + + if(!all_organ_slots.len) + for(var/obj/item/organ/an_organ as anything in subtypesof(/obj/item/organ)) + if(!initial(an_organ.slot)) + continue + all_organ_slots |= initial(an_organ.slot) + + return all_organ_slots diff --git a/code/modules/surgery/organs/external/_external_organ.dm b/code/modules/surgery/organs/external/_visual_organs.dm similarity index 75% rename from code/modules/surgery/organs/external/_external_organ.dm rename to code/modules/surgery/organs/external/_visual_organs.dm index f41cb2a82bd8c..3aa54f27418cc 100644 --- a/code/modules/surgery/organs/external/_external_organ.dm +++ b/code/modules/surgery/organs/external/_visual_organs.dm @@ -1,19 +1,11 @@ -/** -* 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 +/* +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." - - organ_flags = ORGAN_ORGANIC | ORGAN_EDIBLE - visual = TRUE - +/obj/item/organ ///The overlay datum that actually draws stuff on the limb var/datum/bodypart_overlay/mutant/bodypart_overlay - ///If not null, overrides the appearance with this sprite accessory datum - var/sprite_accessory_override /// The savefile_key of the preference this relates to. Used for the preferences UI. var/preference @@ -23,21 +15,24 @@ ///Set to EXTERNAL_BEHIND, EXTERNAL_FRONT or EXTERNAL_ADJACENT if you want to draw one of those layers as the object sprite. FALSE to use your own ///This will not work if it doesn't have a limb to generate its icon with var/use_mob_sprite_as_obj_sprite = FALSE + ///Does this organ have any bodytypes to pass to its bodypart_owner? var/external_bodytypes = NONE ///Does this organ have any bodyshapes to pass to its bodypart_owner? var/external_bodyshapes = NONE + ///Which flags does a 'modification tool' need to have to restyle us, if it all possible (located in code/_DEFINES/mobs) var/restyle_flags = NONE -/**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 + ///If not null, overrides the appearance with this sprite accessory datum + var/sprite_accessory_override + +/**accessory_type 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, accessory_type) - . = ..() - - bodypart_overlay = new bodypart_overlay() +/obj/item/organ/proc/setup_bodypart_overlay(accessory_type) + bodypart_overlay = new bodypart_overlay(src) accessory_type = accessory_type ? accessory_type : sprite_accessory_override var/update_overlays = TRUE @@ -55,61 +50,13 @@ if(restyle_flags) RegisterSignal(src, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle)) -/obj/item/organ/external/Insert(mob/living/carbon/receiver, special, movement_flags) - . = ..() - receiver.update_body_parts() - -/obj/item/organ/external/Remove(mob/living/carbon/organ_owner, special, movement_flags) - . = ..() - if(!special) - organ_owner.update_body_parts() - -/obj/item/organ/external/mob_insert(mob/living/carbon/receiver, special, movement_flags) - if(!should_external_organ_apply_to(type, receiver)) - stack_trace("adding a [type] to a [receiver.type] when it shouldn't be!") - - . = ..() - - if(!.) - return - - if(bodypart_overlay.imprint_on_next_insertion) //We only want this set *once* - var/feature_name = receiver.dna.features[bodypart_overlay.feature_key] - if (isnull(feature_name)) - feature_name = receiver.dna.species.external_organs[type] - bodypart_overlay.set_appearance_from_name(feature_name) - bodypart_overlay.imprint_on_next_insertion = FALSE - - if(external_bodytypes) - receiver.synchronize_bodytypes() - if(external_bodyshapes) - receiver.synchronize_bodyshapes() - - receiver.update_body_parts() - -/obj/item/organ/external/mob_remove(mob/living/carbon/organ_owner, special, moving) - if(!special) - organ_owner.synchronize_bodytypes() - organ_owner.synchronize_bodyshapes() - organ_owner.update_body_parts() - return ..() - -/obj/item/organ/external/on_bodypart_insert(obj/item/bodypart/bodypart) - bodypart.add_bodypart_overlay(bodypart_overlay) - return ..() - -/obj/item/organ/external/on_bodypart_remove(obj/item/bodypart/bodypart) - bodypart.remove_bodypart_overlay(bodypart_overlay) - - if(use_mob_sprite_as_obj_sprite) - update_appearance(UPDATE_OVERLAYS) - - color = bodypart_overlay.draw_color // so a pink felinid doesn't drop a gray tail - return ..() +/// Some sanity checks, but mostly to check if the person has their preference/dna set to load +/proc/should_visual_organ_apply_to(obj/item/organ/organpath, mob/living/carbon/target) + if(!initial(organpath.bodypart_overlay)) + return TRUE -/proc/should_external_organ_apply_to(obj/item/organ/external/organpath, mob/living/carbon/target) if(isnull(organpath) || isnull(target)) - stack_trace("passed a null path or mob to 'should_external_organ_apply_to'") + stack_trace("passed a null path or mob to 'should_visual_organ_apply_to'") return FALSE var/datum/bodypart_overlay/mutant/bodypart_overlay = initial(organpath.bodypart_overlay) @@ -122,7 +69,7 @@ return FALSE ///Update our features after something changed our appearance -/obj/item/organ/external/proc/mutate_feature(features, mob/living/carbon/human/human) +/obj/item/organ/proc/mutate_feature(features, mob/living/carbon/human/human) if(!dna_block) return @@ -131,7 +78,7 @@ bodypart_overlay.set_appearance_from_name(feature_list[deconstruct_block(get_uni_feature_block(features, dna_block), feature_list.len)]) ///If you need to change an external_organ for simple one-offs, use this. Pass the accessory type : /datum/accessory/something -/obj/item/organ/external/proc/simple_change_sprite(accessory_type) +/obj/item/organ/proc/simple_change_sprite(accessory_type) var/datum/sprite_accessory/typed_accessory = accessory_type //we only take types for maintainability bodypart_overlay.set_appearance(typed_accessory) @@ -142,10 +89,7 @@ bodypart_owner.update_icon_dropped() //else if(use_mob_sprite_as_obj_sprite) //are we out in the world, unprotected by flesh? -/obj/item/organ/external/on_life(seconds_per_tick, times_fired) - return - -/obj/item/organ/external/update_overlays() +/obj/item/organ/update_overlays() . = ..() if(!use_mob_sprite_as_obj_sprite) @@ -260,17 +204,16 @@ ///Store our old datum here for if our antennae are healed var/original_sprite_datum -/obj/item/organ/external/antennae/Insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/external/antennae/mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() - if(!.) - return + RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_antennae)) RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_antennae)) -/obj/item/organ/external/antennae/Remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/external/antennae/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() - if(organ_owner) - UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL)) + + UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL)) ///check if our antennae can burn off ;_; /obj/item/organ/external/antennae/proc/try_burn_antennae(mob/living/carbon/human/human) diff --git a/code/modules/surgery/organs/external/restyling.dm b/code/modules/surgery/organs/external/restyling.dm index 7d6be1b6d58e3..f862d9e9c0867 100644 --- a/code/modules/surgery/organs/external/restyling.dm +++ b/code/modules/surgery/organs/external/restyling.dm @@ -1,7 +1,7 @@ -//Contains a bunch of procs for different types, but in the end it just lets you restyle external_organs so thats why its here +//Contains a bunch of procs for different types, but in the end it just lets you restyle the bodypart overlay so thats why its here ///Helper proc to fetch a list of styles a player might want to restyle their features into during the round : returns list("Cabbage" = /datum/sprite_accessory/cabbage) -/obj/item/organ/external/proc/get_valid_restyles() +/obj/item/organ/proc/get_valid_restyles() var/list/valid_restyles valid_restyles = list() @@ -31,18 +31,18 @@ ///Asks the external organs inside the limb if they can restyle /obj/item/bodypart/proc/attempt_feature_restyle(atom/source, mob/living/trimmer, atom/movable/original_target, body_zone, restyle_type, style_speed) var/list/valid_features = list() - for(var/obj/item/organ/external/feature in contents) + for(var/obj/item/organ/feature in contents) if(feature.restyle_flags & restyle_type) valid_features.Add(feature) - var/obj/item/organ/external/target_organ + var/obj/item/organ/target_organ switch(LAZYLEN(valid_features)) if(1) target_organ = valid_features[1] if(2 to INFINITY) var/choose_options = list() var/name_to_organ = list() //literally so I dont have to loop again after someones made their choice - for(var/obj/item/organ/external/organ_choice as anything in valid_features) + for(var/obj/item/organ/organ_choice as anything in valid_features) choose_options[organ_choice.name] = image(organ_choice) name_to_organ[organ_choice.name] = organ_choice var/picked_option = show_radial_menu(trimmer, original_target, choose_options, radius = 38, require_near = TRUE) @@ -57,7 +57,7 @@ target_organ.attempt_feature_restyle(source, trimmer, original_target, body_zone, restyle_type, style_speed) ///Invoke async so we dont break signals -/obj/item/organ/external/proc/on_attempt_feature_restyle(atom/source, mob/living/trimmer, atom/movable/original_target, body_zone, restyle_type, style_speed) +/obj/item/organ/proc/on_attempt_feature_restyle(atom/source, mob/living/trimmer, atom/movable/original_target, body_zone, restyle_type, style_speed) SIGNAL_HANDLER if(restyle_flags & restyle_type) @@ -66,7 +66,7 @@ to_chat(trimmer, span_warning("This tool is incompatible with the [src.name]!")) ///Restyles the external organ from a list of valid options -/obj/item/organ/external/proc/attempt_feature_restyle(atom/source, mob/living/trimmer, atom/movable/original_target, body_zone, restyle_type, style_speed) +/obj/item/organ/proc/attempt_feature_restyle(atom/source, mob/living/trimmer, atom/movable/original_target, body_zone, restyle_type, style_speed) var/list/restyles = get_valid_restyles() var/new_style = tgui_input_list(trimmer, "Select a new style", "Grooming", restyles) @@ -80,5 +80,4 @@ span_notice("You successfully change [original_target == trimmer ? "your" : original_target.name + "'s"] [name].") ) - simple_change_sprite(restyles[new_style]) //turn name to type and pass it on diff --git a/code/modules/surgery/organs/external/spines.dm b/code/modules/surgery/organs/external/spines.dm index 86bff9a768939..214c58df09923 100644 --- a/code/modules/surgery/organs/external/spines.dm +++ b/code/modules/surgery/organs/external/spines.dm @@ -14,13 +14,13 @@ bodypart_overlay = /datum/bodypart_overlay/mutant/spines -/obj/item/organ/external/spines/Insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/external/spines/mob_insert(mob/living/carbon/receiver, special, movement_flags) // If we have a tail, attempt to add a tail spines overlay var/obj/item/organ/external/tail/our_tail = receiver.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) our_tail?.try_insert_tail_spines(our_tail.bodypart_owner) return ..() -/obj/item/organ/external/spines/Remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/external/spines/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) // If we have a tail, remove any tail spines overlay var/obj/item/organ/external/tail/our_tail = organ_owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) our_tail?.remove_tail_spines(our_tail.bodypart_owner) diff --git a/code/modules/surgery/organs/external/tails.dm b/code/modules/surgery/organs/external/tails.dm index 1a52bbe56cd5b..cae83153bdc55 100644 --- a/code/modules/surgery/organs/external/tails.dm +++ b/code/modules/surgery/organs/external/tails.dm @@ -20,7 +20,7 @@ ///The overlay for tail spines, if any var/datum/bodypart_overlay/mutant/tail_spines/tail_spines_overlay -/obj/item/organ/external/tail/Insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/external/tail/mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(.) receiver.clear_mood_event("tail_lost") @@ -34,7 +34,7 @@ // If it's not your tail AND of different species, we are horrified if(IS_WEAKREF_OF(receiver, original_owner)) receiver.add_mood_event("tail_regained", /datum/mood_event/tail_regained_right) - else if(type in receiver.dna.species.external_organs) + else if(type in receiver.dna.species.mutant_organs) receiver.add_mood_event("tail_regained", /datum/mood_event/tail_regained_species) else receiver.add_mood_event("tail_regained", /datum/mood_event/tail_regained_wrong) @@ -83,7 +83,7 @@ organ_owner.clear_mood_event("tail_regained") - if(type in organ_owner.dna.species.external_organs) + if(type in organ_owner.dna.species.mutant_organs) organ_owner.add_mood_event("tail_lost", /datum/mood_event/tail_lost) organ_owner.add_mood_event("tail_balance_lost", /datum/mood_event/tail_balance_lost) diff --git a/code/modules/surgery/organs/external/wings/functional_wings.dm b/code/modules/surgery/organs/external/wings/functional_wings.dm index f4a5a23bf69ef..e1f364f547559 100644 --- a/code/modules/surgery/organs/external/wings/functional_wings.dm +++ b/code/modules/surgery/organs/external/wings/functional_wings.dm @@ -35,15 +35,14 @@ QDEL_NULL(fly) return ..() -/obj/item/organ/external/wings/functional/Insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/external/wings/functional/mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() - if(!.) - return + if(QDELETED(fly)) fly = new fly.Grant(receiver) -/obj/item/organ/external/wings/functional/Remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/external/wings/functional/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() fly?.Remove(organ_owner) if(wings_open) diff --git a/code/modules/surgery/organs/internal/appendix/_appendix.dm b/code/modules/surgery/organs/internal/appendix/_appendix.dm index 83ed8da84aca0..169495bccaa33 100644 --- a/code/modules/surgery/organs/internal/appendix/_appendix.dm +++ b/code/modules/surgery/organs/internal/appendix/_appendix.dm @@ -6,7 +6,7 @@ name = "appendix" icon_state = "appendix" base_icon_state = "appendix" - visual = FALSE + zone = BODY_ZONE_PRECISE_GROIN slot = ORGAN_SLOT_APPENDIX food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/toxin/bad_food = 5) diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm index d4c4b57d75f6b..aa67fe0c08de8 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm @@ -27,14 +27,13 @@ eye_owner.remove_traits(HUD_traits, ORGAN_TRAIT) balloon_alert(eye_owner, "hud enabled") -/obj/item/organ/internal/cyberimp/eyes/hud/Insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags) +/obj/item/organ/internal/cyberimp/eyes/hud/mob_insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags) . = ..() - if(!.) - return + eye_owner.add_traits(HUD_traits, ORGAN_TRAIT) toggled_on = TRUE -/obj/item/organ/internal/cyberimp/eyes/hud/Remove(mob/living/carbon/eye_owner, special, movement_flags) +/obj/item/organ/internal/cyberimp/eyes/hud/mob_remove(mob/living/carbon/eye_owner, special, movement_flags) . = ..() eye_owner.remove_traits(HUD_traits, ORGAN_TRAIT) toggled_on = FALSE diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm index f71e29631b384..09955f45b0349 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm @@ -2,7 +2,7 @@ /obj/item/organ/internal/cyberimp name = "cybernetic implant" desc = "A state-of-the-art implant that improves a baseline's functionality." - visual = FALSE + organ_flags = ORGAN_ROBOTIC failing_desc = "seems to be broken." var/implant_color = COLOR_WHITE diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm index eba24086cca2b..53b95b54d9bcf 100644 --- a/code/modules/surgery/organs/internal/ears/_ears.dm +++ b/code/modules/surgery/organs/internal/ears/_ears.dm @@ -4,7 +4,6 @@ desc = "There are three parts to the ear. Inner, middle and outer. Only one of these parts should be normally visible." zone = BODY_ZONE_HEAD slot = ORGAN_SLOT_EARS - visual = FALSE gender = PLURAL healing_factor = STANDARD_ORGAN_HEALING @@ -136,28 +135,34 @@ icon_state = "kitty" visual = TRUE damage_multiplier = 2 - // Keeps track of which cat ears sprite is associated with this. - var/variant = "Cat" -/obj/item/organ/internal/ears/cat/Initialize(mapload, variant_pref) - . = ..() - if(variant_pref) - variant = variant_pref + preference = "feature_human_ears" -/obj/item/organ/internal/ears/cat/on_mob_insert(mob/living/carbon/human/ear_owner) - . = ..() - if(istype(ear_owner) && ear_owner.dna) - color = ear_owner.hair_color - ear_owner.dna.features["ears"] = ear_owner.dna.species.mutant_bodyparts["ears"] = variant - ear_owner.dna.update_uf_block(DNA_EARS_BLOCK) - ear_owner.update_body() + dna_block = DNA_EARS_BLOCK -/obj/item/organ/internal/ears/cat/on_mob_remove(mob/living/carbon/human/ear_owner) - . = ..() - if(istype(ear_owner) && ear_owner.dna) - color = ear_owner.hair_color - ear_owner.dna.species.mutant_bodyparts -= "ears" - ear_owner.update_body() + bodypart_overlay = /datum/bodypart_overlay/mutant/cat_ears + +/// Bodypart overlay for the horrible cat ears +/datum/bodypart_overlay/mutant/cat_ears + layers = EXTERNAL_FRONT | EXTERNAL_ADJACENT + color_source = ORGAN_COLOR_HAIR + feature_key = "ears" + + /// We dont color the inner part, which is the front layer + var/colorless_layer = EXTERNAL_FRONT + +/datum/bodypart_overlay/mutant/cat_ears/get_global_feature_list() + return SSaccessories.ears_list + +/datum/bodypart_overlay/mutant/cat_ears/can_draw_on_bodypart(mob/living/carbon/human/human) + if((human.head?.flags_inv & HIDEHAIR) || (human.wear_mask?.flags_inv & HIDEHAIR)) + return FALSE + return TRUE + +/datum/bodypart_overlay/mutant/cat_ears/color_image(image/overlay, draw_layer, obj/item/bodypart/limb) + if(draw_layer != bitflag_to_layer(colorless_layer)) + return ..() + return overlay /obj/item/organ/internal/ears/penguin name = "penguin ears" diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm index bffdf1ae44d08..4b611852b79b7 100644 --- a/code/modules/surgery/organs/internal/eyes/_eyes.dm +++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm @@ -52,21 +52,18 @@ /// Native FOV that will be applied if a config is enabled var/native_fov = FOV_90_DEGREES -/obj/item/organ/internal/eyes/Insert(mob/living/carbon/eye_recipient, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/internal/eyes/mob_insert(mob/living/carbon/receiver, special, movement_flags) // If we don't do this before everything else, heterochromia will be reset leading to eye_color_right no longer being accurate - if(ishuman(eye_recipient)) - var/mob/living/carbon/human/human_recipient = eye_recipient + if(ishuman(receiver)) + var/mob/living/carbon/human/human_recipient = receiver old_eye_color_left = human_recipient.eye_color_left old_eye_color_right = human_recipient.eye_color_right . = ..() - if(!.) - return - - eye_recipient.cure_blind(NO_EYES) + receiver.cure_blind(NO_EYES) apply_damaged_eye_effects() - refresh(eye_recipient, call_update = TRUE) + refresh(receiver, call_update = TRUE) /// Refreshes the visuals of the eyes /// If call_update is TRUE, we also will call update_body @@ -94,31 +91,32 @@ if(call_update) affected_human.update_body() -/obj/item/organ/internal/eyes/Remove(mob/living/carbon/eye_owner, special, movement_flags) +/obj/item/organ/internal/eyes/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() - if(ishuman(eye_owner)) - var/mob/living/carbon/human/human_owner = eye_owner + + if(ishuman(organ_owner)) + var/mob/living/carbon/human/human_owner = organ_owner if(initial(eye_color_left)) human_owner.eye_color_left = old_eye_color_left if(initial(eye_color_right)) human_owner.eye_color_right = old_eye_color_right if(native_fov) - eye_owner.remove_fov_trait(type) + organ_owner.remove_fov_trait(type) if(!special) human_owner.update_body() // Cure blindness from eye damage - eye_owner.cure_blind(EYE_DAMAGE) - eye_owner.cure_nearsighted(EYE_DAMAGE) + organ_owner.cure_blind(EYE_DAMAGE) + organ_owner.cure_nearsighted(EYE_DAMAGE) // Eye blind and temp blind go to, even if this is a bit of cheesy way to clear blindness - eye_owner.remove_status_effect(/datum/status_effect/eye_blur) - eye_owner.remove_status_effect(/datum/status_effect/temporary_blindness) + organ_owner.remove_status_effect(/datum/status_effect/eye_blur) + organ_owner.remove_status_effect(/datum/status_effect/temporary_blindness) // Then become blind anyways (if not special) if(!special) - eye_owner.become_blind(NO_EYES) + organ_owner.become_blind(NO_EYES) - eye_owner.update_tint() - eye_owner.update_sight() + organ_owner.update_tint() + organ_owner.update_sight() #define OFFSET_X 1 #define OFFSET_Y 2 @@ -428,7 +426,7 @@ deactivate(close_ui = TRUE) /// Set the initial color of the eyes on insert to be the mob's previous eye color. -/obj/item/organ/internal/eyes/robotic/glow/Insert(mob/living/carbon/eye_recipient, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/internal/eyes/robotic/glow/mob_insert(mob/living/carbon/eye_recipient, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() left_eye_color_string = old_eye_color_left right_eye_color_string = old_eye_color_right diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index b0fac316ad3e3..d52e483da389d 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -3,7 +3,7 @@ desc = "I feel bad for the heartless bastard who lost this." icon_state = "heart-on" base_icon_state = "heart" - visual = FALSE + zone = BODY_ZONE_CHEST slot = ORGAN_SLOT_HEART item_flags = NO_BLOOD_ON_ITEM diff --git a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm index bd30318a72225..3e853a965b1bf 100644 --- a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm +++ b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm @@ -21,15 +21,14 @@ add_atom_colour(ethereal_color, FIXED_COLOUR_PRIORITY) update_appearance() -/obj/item/organ/internal/heart/ethereal/Insert(mob/living/carbon/heart_owner, special = FALSE, movement_flags) +/obj/item/organ/internal/heart/ethereal/mob_insert(mob/living/carbon/heart_owner, special = FALSE, movement_flags) . = ..() - if(!.) - return + RegisterSignal(heart_owner, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_change)) RegisterSignal(heart_owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_owner_fully_heal)) RegisterSignal(heart_owner, COMSIG_QDELETING, PROC_REF(owner_deleted)) -/obj/item/organ/internal/heart/ethereal/Remove(mob/living/carbon/heart_owner, special, movement_flags) +/obj/item/organ/internal/heart/ethereal/mob_remove(mob/living/carbon/heart_owner, special, movement_flags) UnregisterSignal(heart_owner, list(COMSIG_MOB_STATCHANGE, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_QDELETING)) REMOVE_TRAIT(heart_owner, TRAIT_CORPSELOCKED, SPECIES_TRAIT) stop_crystalization_process(heart_owner) diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index ef21595faf0fd..3933a9efa5930 100755 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -7,7 +7,7 @@ name = "liver" desc = "Pairing suggestion: chianti and fava beans." icon_state = "liver" - visual = FALSE + w_class = WEIGHT_CLASS_SMALL zone = BODY_ZONE_CHEST slot = ORGAN_SLOT_LIVER diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 47078bfc8038e..9b38803dd7743 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -1,7 +1,7 @@ /obj/item/organ/internal/lungs name = "lungs" icon_state = "lungs" - visual = FALSE + zone = BODY_ZONE_CHEST slot = ORGAN_SLOT_LUNGS gender = PLURAL @@ -154,17 +154,16 @@ add_gas_reaction(/datum/gas/zauker, while_present = PROC_REF(too_much_zauker)) ///Simply exists so that you don't keep any alerts from your previous lack of lungs. -/obj/item/organ/internal/lungs/Insert(mob/living/carbon/receiver, special = FALSE, movement_flags) +/obj/item/organ/internal/lungs/mob_insert(mob/living/carbon/receiver, special = FALSE, movement_flags) . = ..() - if(!.) - return . + receiver.clear_alert(ALERT_NOT_ENOUGH_OXYGEN) receiver.clear_alert(ALERT_NOT_ENOUGH_CO2) receiver.clear_alert(ALERT_NOT_ENOUGH_NITRO) receiver.clear_alert(ALERT_NOT_ENOUGH_PLASMA) receiver.clear_alert(ALERT_NOT_ENOUGH_N2O) -/obj/item/organ/internal/lungs/Remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/internal/lungs/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() // This is very "manual" I realize, but it's useful to ensure cleanup for gases we're removing happens // Avoids stuck alerts and such diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index 3b6cf14e84464..ba526aff2e0f7 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -5,7 +5,7 @@ name = "stomach" desc = "Onaka ga suite imasu." icon_state = "stomach" - visual = FALSE + w_class = WEIGHT_CLASS_SMALL zone = BODY_ZONE_CHEST slot = ORGAN_SLOT_STOMACH @@ -245,11 +245,11 @@ disgusted.throw_alert(ALERT_DISGUST, /atom/movable/screen/alert/disgusted) disgusted.add_mood_event("disgust", /datum/mood_event/disgusted) -/obj/item/organ/internal/stomach/Insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/internal/stomach/mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() receiver.hud_used?.hunger?.update_appearance() -/obj/item/organ/internal/stomach/Remove(mob/living/carbon/stomach_owner, special, movement_flags) +/obj/item/organ/internal/stomach/mob_remove(mob/living/carbon/stomach_owner, special, movement_flags) if(ishuman(stomach_owner)) var/mob/living/carbon/human/human_owner = owner human_owner.clear_alert(ALERT_DISGUST) diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 13c1a8e881ede..f969741ce3313 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -2,7 +2,7 @@ name = "tongue" desc = "A fleshy muscle mostly used for lying." icon_state = "tongue" - visual = FALSE + zone = BODY_ZONE_PRECISE_MOUTH slot = ORGAN_SLOT_TONGUE attack_verb_continuous = list("licks", "slobbers", "slaps", "frenches", "tongues") @@ -125,30 +125,30 @@ food_taste_reaction = FOOD_LIKED return food_taste_reaction -/obj/item/organ/internal/tongue/Insert(mob/living/carbon/tongue_owner, special = FALSE, movement_flags) +/obj/item/organ/internal/tongue/mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() - if(!.) - return + if(modifies_speech) - RegisterSignal(tongue_owner, COMSIG_MOB_SAY, PROC_REF(handle_speech)) - tongue_owner.voice_filter = voice_filter + RegisterSignal(receiver, COMSIG_MOB_SAY, PROC_REF(handle_speech)) + receiver.voice_filter = voice_filter /* This could be slightly simpler, by making the removal of the * NO_TONGUE_TRAIT conditional on the tongue's `sense_of_taste`, but * then you can distinguish between ageusia from no tongue, and * ageusia from having a non-tasting tongue. */ - REMOVE_TRAIT(tongue_owner, TRAIT_AGEUSIA, NO_TONGUE_TRAIT) + REMOVE_TRAIT(receiver, TRAIT_AGEUSIA, NO_TONGUE_TRAIT) apply_tongue_effects() -/obj/item/organ/internal/tongue/Remove(mob/living/carbon/tongue_owner, special, movement_flags) +/obj/item/organ/internal/tongue/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() + temp_say_mod = "" - UnregisterSignal(tongue_owner, COMSIG_MOB_SAY) - REMOVE_TRAIT(tongue_owner, TRAIT_SPEAKS_CLEARLY, SPEAKING_FROM_TONGUE) - REMOVE_TRAIT(tongue_owner, TRAIT_AGEUSIA, ORGAN_TRAIT) + UnregisterSignal(organ_owner, COMSIG_MOB_SAY) + REMOVE_TRAIT(organ_owner, TRAIT_SPEAKS_CLEARLY, SPEAKING_FROM_TONGUE) + REMOVE_TRAIT(organ_owner, TRAIT_AGEUSIA, ORGAN_TRAIT) // Carbons by default start with NO_TONGUE_TRAIT caused TRAIT_AGEUSIA - ADD_TRAIT(tongue_owner, TRAIT_AGEUSIA, NO_TONGUE_TRAIT) - tongue_owner.voice_filter = initial(tongue_owner.voice_filter) + ADD_TRAIT(organ_owner, TRAIT_AGEUSIA, NO_TONGUE_TRAIT) + organ_owner.voice_filter = initial(organ_owner.voice_filter) /obj/item/organ/internal/tongue/apply_organ_damage(damage_amount, maximum = maxHealth, required_organ_flag) . = ..() diff --git a/code/modules/surgery/organs/internal/vocal_cords/_vocal_cords.dm b/code/modules/surgery/organs/internal/vocal_cords/_vocal_cords.dm index 9183c7eb80944..f2d9abae41669 100644 --- a/code/modules/surgery/organs/internal/vocal_cords/_vocal_cords.dm +++ b/code/modules/surgery/organs/internal/vocal_cords/_vocal_cords.dm @@ -1,7 +1,6 @@ /obj/item/organ/internal/vocal_cords //organs that are activated through speech with the :x/MODE_KEY_VOCALCORDS channel name = "vocal cords" icon_state = "appendix" - visual = FALSE zone = BODY_ZONE_PRECISE_MOUTH slot = ORGAN_SLOT_VOICE gender = PLURAL @@ -87,7 +86,6 @@ next_command = world.time + (cooldown * cooldown_mod) /obj/item/organ/internal/adamantine_resonator - visual = FALSE name = "adamantine resonator" desc = "Fragments of adamantine exist in all golems, stemming from their origins as purely magical constructs. These are used to \"hear\" messages from their leaders." zone = BODY_ZONE_HEAD diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm index ff9f753ce18a1..010daa3fd24c5 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -18,7 +18,8 @@ mob_insert(receiver, special, movement_flags) bodypart_insert(limb_owner = receiver, movement_flags = movement_flags) - return TRUE + if(!special) + receiver.update_body_parts() /* * Remove the organ from the select mob. @@ -32,6 +33,9 @@ mob_remove(organ_owner, special, movement_flags) bodypart_remove(limb_owner = organ_owner, movement_flags = movement_flags) + if(!special) + organ_owner.update_body_parts() + /* * Insert the organ into the select mob. * @@ -65,6 +69,11 @@ wash(CLEAN_TYPE_BLOOD) organ_flags &= ~ORGAN_VIRGIN + if(external_bodytypes) + receiver.synchronize_bodytypes() + if(external_bodyshapes) + receiver.synchronize_bodyshapes() + receiver.organs |= src receiver.organs_slot[slot] = src owner = receiver @@ -120,6 +129,9 @@ ADD_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT) interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP + if(bodypart_overlay) + limb.add_bodypart_overlay(bodypart_overlay) + /* * Remove the organ from the select mob. * @@ -163,6 +175,9 @@ SEND_SIGNAL(organ_owner, COMSIG_CARBON_LOSE_ORGAN, src, special) ADD_TRAIT(src, TRAIT_USED_ORGAN, ORGAN_TRAIT) + organ_owner.synchronize_bodytypes() + organ_owner.synchronize_bodyshapes() + var/list/diseases = organ_owner.get_static_viruses() if(!LAZYLEN(diseases)) return @@ -211,6 +226,16 @@ REMOVE_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT) interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP + if(!bodypart_overlay) + return + + limb.remove_bodypart_overlay(bodypart_overlay) + + if(use_mob_sprite_as_obj_sprite) + update_appearance(UPDATE_OVERLAYS) + + color = bodypart_overlay.draw_color // so a pink felinid doesn't drop a gray tail + /// In space station videogame, nothing is sacred. If somehow an organ is removed unexpectedly, handle it properly /obj/item/organ/proc/forced_removal() SIGNAL_HANDLER diff --git a/code/modules/unit_tests/organ_bodypart_shuffle.dm b/code/modules/unit_tests/organ_bodypart_shuffle.dm index 842dd1c6c1344..11c0bcd71becb 100644 --- a/code/modules/unit_tests/organ_bodypart_shuffle.dm +++ b/code/modules/unit_tests/organ_bodypart_shuffle.dm @@ -2,7 +2,7 @@ /datum/unit_test/organ_bodypart_shuffle /datum/unit_test/organ_bodypart_shuffle/Run() - var/mob/living/carbon/human/hollow_boy = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/hollow_boy = allocate(/mob/living/carbon/human/consistent) //freshly filled with wet insides // Test if organs are all properly updating when forcefully removed var/list/removed_organs = list() @@ -30,5 +30,3 @@ continue TEST_ASSERT(organ in hollow_boy.organs, "Organ '[organ.name] was put in an empty bodypart that replaced a humans, but the organ did not come with.") - // Test if bodyparts are all properly updating when forcefully removed - hollow_boy = allocate(/mob/living/carbon/human/consistent) //freshly filled with wet insides diff --git a/code/modules/unit_tests/organ_set_bonus.dm b/code/modules/unit_tests/organ_set_bonus.dm index 967803e223f17..1231ddd5c6670 100644 --- a/code/modules/unit_tests/organ_set_bonus.dm +++ b/code/modules/unit_tests/organ_set_bonus.dm @@ -30,7 +30,7 @@ // Attempt to insert entire list of mutant organs for the given infusion_entry. for(var/obj/item/organ/organ as anything in output_organs) organ = new organ() - TEST_ASSERT(organ.Insert(lab_rat, special = TRUE, movement_flags = DELETE_IF_REPLACED), "The organ `[organ.type]` for `[infuser_entry.type]` was not inserted in the mob when expected, Insert() returned falsy when TRUE was expected.") + organ.Insert(lab_rat, special = TRUE, movement_flags = DELETE_IF_REPLACED) inserted_organs += organ // Search for added Status Effect. diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm index 67a3a41a48828..7adefcaa5eda6 100644 --- a/code/modules/wiremod/shell/brain_computer_interface.dm +++ b/code/modules/wiremod/shell/brain_computer_interface.dm @@ -3,7 +3,6 @@ desc = "An implant that can be placed in a user's head to control circuits using their brain." icon = 'icons/obj/science/circuits.dmi' icon_state = "bci" - visual = FALSE zone = BODY_ZONE_HEAD w_class = WEIGHT_CLASS_TINY diff --git a/html/changelogs/AutoChangeLog-pr-12.yml b/html/changelogs/AutoChangeLog-pr-12.yml new file mode 100644 index 0000000000000..135ead26340ec --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-12.yml @@ -0,0 +1,4 @@ +author: "kittysmooch" +delete-after: True +changes: + - admin: "ported ctrl+click spawning as aghost from skyrat\n:cl:" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-13.yml b/html/changelogs/AutoChangeLog-pr-13.yml new file mode 100644 index 0000000000000..7a416b5a21c74 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13.yml @@ -0,0 +1,5 @@ +author: "kittysmooch" +delete-after: True +changes: + - rscadd: "added a diverse range of collars to improve pet safety and identifcation" + - code_imp: "set up dopplerGAGS so that our modular files won't cause runtimes :3c\n:cl:" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-19.yml b/html/changelogs/AutoChangeLog-pr-19.yml new file mode 100644 index 0000000000000..c8c9a092e197c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-19.yml @@ -0,0 +1,4 @@ +author: "CliffracerX" +delete-after: True +changes: + - qol: "ghostroles use your character now!" \ No newline at end of file diff --git a/icons/mob/human/cat_features.dmi b/icons/mob/human/cat_features.dmi index 6e7fd024fee1d..d2d67fdd9b32e 100644 Binary files a/icons/mob/human/cat_features.dmi and b/icons/mob/human/cat_features.dmi differ diff --git a/modular_doppler/icspawn/cconsultant_items.dm b/modular_doppler/icspawn/cconsultant_items.dm new file mode 100644 index 0000000000000..27231fdf4e759 --- /dev/null +++ b/modular_doppler/icspawn/cconsultant_items.dm @@ -0,0 +1,306 @@ +////////// +//datums// +////////// + +/datum/id_trim/admin/ccid + assignment = "Continuity Consultant" + trim_state = "trim_stationengineer" + department_color = COLOR_VOID_PURPLE + subdepartment_color = COLOR_ENGINEERING_ORANGE + +/datum/armor/vest_debug + melee = 95 + melee = 95 + laser = 95 + energy = 95 + bomb = 95 + bio = 95 + fire = 98 + acid = 98 + +/datum/outfit/debug/cconsultant + name = "Continuity Consultant" + uniform = /obj/item/clothing/under/syndicate/combat + belt = /obj/item/storage/belt/utility/chief/full/debug + shoes = /obj/item/clothing/shoes/combat/debug + id = /obj/item/card/id/advanced/debug/ccid + box = /obj/item/storage/box/debugtools + backpack_contents = list( + /obj/item/storage/part_replacer/bluespace/tier4/cconsultant = 1, + /obj/item/gun/magic/wand/resurrection/debug = 1, + /obj/item/gun/magic/wand/death/debug = 1, + /obj/item/debug/human_spawner = 1, + /obj/item/debug/omnitool = 1, + /obj/item/storage/box/stabilized = 1, + ) + +/datum/outfit/admin/cconsultant + name = "Continuity Consultant (MODsuit)" + uniform = /obj/item/clothing/under/syndicate/combat + belt = /obj/item/storage/belt/utility/chief/full/debug + shoes = /obj/item/clothing/shoes/combat/debug + id = /obj/item/card/id/advanced/debug/ccid + box = /obj/item/storage/box/debugtools + backpack_contents = list( + /obj/item/storage/part_replacer/bluespace/tier4/cconsultant = 1, + /obj/item/gun/magic/wand/resurrection/debug = 1, + /obj/item/gun/magic/wand/death/debug = 1, + /obj/item/debug/human_spawner = 1, + /obj/item/debug/omnitool = 1, + /obj/item/storage/box/stabilized = 1, + ) + +///////// +//items// +///////// + +/obj/item/card/id/advanced/debug/ccid + name = "\improper Continuity Consultant ID" + desc = "An obscure ID card. In your peripheral vision the plasticene surface swims with flowing color." + icon_state = "card_centcom" + assigned_icon_state = "assigned_centcom" + trim = /datum/id_trim/admin/ccid + wildcard_slots = WILDCARD_LIMIT_ADMIN + +/obj/item/clothing/suit/armor/vest/debug + name = "Continuity Consultant vest" + desc = "A sleek piece of armour designed for Bluespace agents." + armor_type = /datum/armor/vest_debug + w_class = WEIGHT_CLASS_TINY + +/obj/item/clothing/shoes/combat/debug + w_class = WEIGHT_CLASS_TINY + +/obj/item/storage/belt/utility/chief/full/debug + name = "\improper Continuity Consultant's belt" + w_class = WEIGHT_CLASS_TINY + +/obj/item/storage/part_replacer/bluespace/tier4/cconsultant + name = "\improper Continuity Consultant RPED" + desc = "A specialized bluespace RPED that can manufacture stock parts on the fly. Alt-Right-Click to manufacture parts, change settings, or clear its internal storage." + /// Whether or not auto-clear is enabled + var/auto_clear = TRUE + /// List of valid types for pick_stock_part(). + var/static/list/valid_stock_part_types = list( + /obj/item/circuitboard/machine, + /obj/item/stock_parts, + /obj/item/reagent_containers/cup/beaker, + ) + +/obj/item/storage/part_replacer/bluespace/tier4/cconsultant/Initialize(mapload) + . = ..() + atom_storage.max_slots = 1000 + atom_storage.max_total_storage = 20000 + +/// An extension to the default RPED part replacement action - if you don't have the requisite parts in the RPED already, it will spawn T4 versions to use. +/obj/item/storage/part_replacer/bluespace/tier4/cconsultant/part_replace_action(obj/attacked_object, mob/living/user) + // We start with setting up a list of the current contents of the RPED when using auto-clear. This is used to detect new items after upgrades are applied & remove them. + var/list/old_contents = list() + var/list/inv_grab = atom_storage.return_inv(FALSE) + if(auto_clear) + old_contents = atom_storage.return_inv(FALSE) + // Once old_contents has been initialized, if needed, we check if the target object is a machine frame. + var/obj/structure/frame/attacked_frame = attacked_object + if(istype(attacked_frame, /obj/structure/frame/machine)) + var/obj/structure/frame/machine/machine_frame = attacked_frame + var/obj/item/circuitboard/machine/circuit = machine_frame.circuit + // Prioritize using the circuit's components list first, if present, to maintain consistency. + if(istype(circuit)) + spawn_parts_for_components(user, circuit.req_components) + else if(machine_frame.req_components) + spawn_parts_for_components(user, machine_frame.req_components) + else + // It's not a machine frame, so let's check if it's a regular machine. + if(ismachinery(attacked_object) && !istype(attacked_object, /obj/machinery/computer)) + var/obj/machinery/attacked_machinery = attacked_object + var/obj/item/circuitboard/machine/circuit = attacked_machinery.circuit + // If it is, we need to use the circuit's components; there's no good way to get required components off of an already-built machine. + if(istype(circuit)) + spawn_parts_for_components(user, circuit.req_components) + . = ..() + // If auto-clear is in use, + if(auto_clear) + inv_grab.Cut() + inv_grab = atom_storage.return_inv(FALSE) + for(var/obj/item/stored_item in inv_grab) + if(!(stored_item in old_contents)) + qdel(stored_item) + +/// A bespoke proc for spawning in parts +/obj/item/storage/part_replacer/bluespace/tier4/cconsultant/proc/spawn_parts_for_components(mob/living/user, list/required_components) + // Since req_components in machineboards can list item types *OR* /datum/stock_part subtypes this gets a little complicated. + var/list/subtypes = list() + for(var/req_component in required_components) + // Start off noting how many the recipe calls for, a counter for how many matching parts have been found, and generating a list of subtypes for use in later checks. + var/parts_amount_required = required_components[req_component] + var/found_matching = 0 + subtypes = typesof(req_component) + + if(!parts_amount_required) + continue + + /// Then, check if the requested component is an object subtype - this means it's probably either materials (e.g, cables) or non-stock_part subtypes like beakers. + if(ispath(req_component, /obj/item)) + // If it's a stack, it needs special treatment. + if(ispath(req_component, /obj/item/stack)) + // Stacks generate the matching count based on how many matching stacks are in the RPED's inventory with sufficient count. + // To find stacks inside the RPED, we search its contents for anything that's a subtype of /obj/item/stack. + for(var/obj/stored_item in contents) + var/obj/item/stack/stored_item_as_stack = stored_item + if(istype(stored_item_as_stack)) + // If a stack item is found, we check if it's in the typesof list for the current requested component, and if so, mark its count. + if(stored_item_as_stack.type in subtypes) + found_matching += stored_item_as_stack.amount + // If there's enough, we can return early. + if(found_matching >= parts_amount_required) + break + // If there's not enough left, spawn enough of the appropriate type that there will be. Stacks' Initialialize accepts an amount for the newly-spawned stack to have, and will auto-split as needed. + if(found_matching < parts_amount_required) + atom_storage.attempt_insert(new req_component(src, parts_amount_required - found_matching), user, TRUE) + continue + else + // It's not a stack, which means now we have to count how many matching items are present. + for(var/obj/stored_item in contents) + if(stored_item.type in subtypes) + found_matching += 1 + // If there's enough, we can break - no need to spawn extras. + if(found_matching >= parts_amount_required) + break + // If there's still not enough, we're going to have to spawn enough in manually. + if(found_matching < parts_amount_required) + for(var/i in 1 to parts_amount_required - found_matching) + atom_storage.attempt_insert(new req_component(src), user, TRUE) + continue + + /// If it's not an obj, then it's a subtype of /datum/stock_part - or *should be*, anyway. + else if(ispath(req_component, /datum/stock_part)) + var/datum/stock_part/part_type = new req_component() + var/base_type = part_type.physical_object_base_type + // Specific machines sometimes call for specific tiers of part; give them precisely what they ask for, just in case. + if(part_type.tier > 1) + base_type = part_type.physical_object_type + // Search to see if we have enough of that exact item, and if not, we'll spawn more. + for(var/obj/stored_item in contents) + if(stored_item.type == base_type) + found_matching += 1 + // If there's enough, we can return early. + if(found_matching >= parts_amount_required) + break + // If there's still not enough, we're going to have to spawn enough in manually. + if(found_matching < parts_amount_required) + for(var/i in 1 to parts_amount_required - found_matching) + atom_storage.attempt_insert(new base_type(src), user, TRUE) + continue + else + // For everything else, just make sure we have enough valid items of the stock part's subtypes. + subtypes = typesof(base_type) + for(var/obj/stored_item in contents) + if(stored_item.type in subtypes) + found_matching += 1 + // If there's enough, we can return early. + if(found_matching >= parts_amount_required) + break + + // If there's still not enough, we're going to have to spawn enough in manually. + if(found_matching < parts_amount_required) + // Reset the subtypes list so we can pick the highest tier of part available. + subtypes = typesof(req_component) + var/highest_tier = 0 + + // Search those subtypes for the highest. This SHOULD only ever go up to 4, but that's on the assumption upstream doesn't change it. + for(var/subtype_path in subtypes) + var/datum/stock_part/sub_part = new subtype_path() + if(sub_part.tier > highest_tier) + highest_tier = sub_part.tier + base_type = sub_part.physical_object_type + + // Once the best component has been found, fill in enough remaining. + for(var/i in 1 to parts_amount_required - found_matching) + atom_storage.attempt_insert(new base_type(src), user, TRUE) + continue + + // If it's not a /datum/stock_part subtype either, something has gone wrong and devs should probably be alerted. + if(found_matching < parts_amount_required) + to_chat(user, span_notice("Something went wrong manufacturing [req_component]. Alert the devs, and let them know what machine it was!")) + +/// cconsultants' special Bluespace RPED can manufacture parts on Alt-RMB, either cables, glass, machine boards, or stock parts. +/obj/item/storage/part_replacer/bluespace/tier4/cconsultant/click_alt_secondary(mob/user) + // Ask the user what they want to make, or if they want to clear the storage. + var/spawn_selection = tgui_input_list(user, "Pick a part, or clear storage", "RPED Manufacture", list("Clear All Items", "Toggle Auto-Clear", "Cables", "Glass", "Spare T4s", "Machine Board", "Stock Part", "Beaker")) + // If they didn't cancel out of the list selection, we do things. Clear-all removes all items, auto-clear destroys left-overs after upgrades, and everything else is pretty self-explanatory. + // Machine boards and stock parts use a recursive subtype selector. + if(isnull(spawn_selection)) + return + else if(spawn_selection == "Clear All Items") + var/list/inv_grab = atom_storage.return_inv(FALSE) + for(var/obj/item/stored_item in inv_grab) + qdel(stored_item) + else if(spawn_selection == "Toggle Auto-Clear") + auto_clear = !auto_clear + to_chat(user, span_notice("The RPED will now [(auto_clear ? "destroy" : "keep")] items left-over after upgrades.")) + else if(spawn_selection == "Cables") + atom_storage.attempt_insert(new /obj/item/stack/cable_coil(src), user, TRUE) + else if(spawn_selection == "Glass") + atom_storage.attempt_insert(new /obj/item/stack/sheet/glass/fifty(src), user, TRUE) + else if(spawn_selection == "Spare T4s") + for(var/i in 1 to 10) + atom_storage.attempt_insert(new /obj/item/stock_parts/capacitor/quadratic(src), user, TRUE) + atom_storage.attempt_insert(new /obj/item/stock_parts/scanning_module/triphasic(src), user, TRUE) + atom_storage.attempt_insert(new /obj/item/stock_parts/servo/femto(src), user, TRUE) + atom_storage.attempt_insert(new /obj/item/stock_parts/micro_laser/quadultra(src), user, TRUE) + atom_storage.attempt_insert(new /obj/item/stock_parts/matter_bin/bluespace(src), user, TRUE) + atom_storage.attempt_insert(new /obj/item/stock_parts/power_store/cell/bluespace(src), user, TRUE) + else + var/subtype + if(spawn_selection == "Machine Board") + subtype = /obj/item/circuitboard/machine + else if(spawn_selection == "Stock Part") + subtype = /obj/item/stock_parts + else if(spawn_selection == "Beaker") + subtype = /obj/item/reagent_containers/cup/beaker + if(subtype) + pick_stock_part(user, FALSE, subtype) + +/// A bespoke proc for picking a subtype to spawn in a relatively user-friendly way. +/obj/item/storage/part_replacer/bluespace/tier4/cconsultant/proc/pick_stock_part(mob/user, recurse, subtype) + // Sanity check: make sure it's actually an item, and not an atom, machine, or whatever else someone might try to feed it down the line. + if(!is_path_in_list(subtype, valid_stock_part_types)) + return + // Stores a list of pretty type names : actual paths. + var/list/items_temp = list() + // Grab the initial list of paths, NOT INCLUDING this specific path. + var/list/paths = subtypesof(subtype) + + // Simplistic check to only list top-level subtypes. + var/list/top_level_subtypes_only = list() + for(var/datum/subtype_path as anything in paths) + if(initial(subtype_path.parent_type) != subtype) + continue + top_level_subtypes_only += subtype_path + paths = top_level_subtypes_only + + // With all sub-subtypes removed, initialize the list of valid, spawnable items & their pretty names - and if this is a recursion, include the original subtype. + if(recurse) + paths += subtype + for(var/path in paths) + var/obj/path_as_obj = path + // Generates a pretty list of item names & paths, including notes for those with subtypes. When browsing subtypes, the parent won't have the (# more) note added. + if(length(subtypesof(path))) + if(path == subtype) + items_temp["[initial(path_as_obj.name)]: [path]"] = path + else + items_temp["[initial(path_as_obj.name)] (+[length(subtypesof(path))] more): [path]"] = path + else + items_temp["[initial(path_as_obj.name)]: [path]"] = path + + // Finally, once the listed is generated, ask the user what they want to spawn. + var/target_item = tgui_input_list(user, "Select Subtype", "RPED Manufacture", sort_list(items_temp)) + if(target_item) + // If they select something, and the name:path binding is valid, then either spawn it, OR, if it has subtypes, and isn't the parent type, recurse to let them pick a subtype. + if(items_temp[target_item]) + var/the_item = items_temp[target_item] + if(length(subtypesof(the_item)) && the_item != subtype) + pick_stock_part(user, TRUE, the_item) + else + for(var/i in 1 to 25) + atom_storage.attempt_insert(new the_item(src), user, TRUE) diff --git a/modular_doppler/icspawn/observer_spawn.dm b/modular_doppler/icspawn/observer_spawn.dm new file mode 100644 index 0000000000000..f3dbf06aac84f --- /dev/null +++ b/modular_doppler/icspawn/observer_spawn.dm @@ -0,0 +1,150 @@ +/mob/dead/observer/CtrlClickOn(mob/user) + quickicspawn(user) + +/mob/dead/observer/proc/quickicspawn(mob/user) + if(isobserver(user) && check_rights(R_SPAWN)) + var/list/outfits = list() + outfits["Continuity Consultant"] = /datum/outfit/debug/cconsultant + outfits["Continuity Consultant (MODsuit)"] = /datum/outfit/admin/cconsultant + outfits["Show All"] = "Show All" + + var/dresscode + var/teleport_option = tgui_alert(usr, "How would you like to be spawned in?", "IC Quick Spawn", list("Bluespace", "Pod", "Cancel")) + if (teleport_option == "Cancel") + return + var/character_option = tgui_alert(usr, "Which character?", "IC Quick Spawn", list("Selected Character", "Randomly Created", "Cancel")) + if (character_option == "Cancel") + return + var/initial_outfits = tgui_alert(usr, "Select outfit", "Quick Dress", list("Continuity Consultant", "Show All", "Cancel")) + if (initial_outfits == "Cancel") + return + + switch(initial_outfits) + if("Continuity Consultant") + dresscode = /datum/outfit/admin/cconsultant + if("Show All") + dresscode = client.robust_dress_shop_skyrat() + if (!dresscode) + return + + // We're spawning someone else + var/give_return + if (user != usr) + give_return = tgui_alert(usr, "Do you want to give them the power to return? Not recommended for non-admins.", "Give power?", list("Yes", "No")) + if(!give_return) + return + + var/addquirks + if(character_option == "Selected Character") + addquirks = tgui_input_list(src, "Include quirks?", "Quirky", list("Quirks & Loadout", "Quirks Only", "Loadout Only", "Neither")) + if(!addquirks) + return + + + var/turf/current_turf = get_turf(user) + var/mob/living/carbon/human/spawned_player = new(user) + + if (character_option == "Selected Character") + spawned_player.name = user.name + spawned_player.real_name = user.real_name + + var/mob/living/carbon/human/player_as_human = spawned_player + user.client?.prefs.safe_transfer_prefs_to(player_as_human) + if(addquirks == "Quirks & Loadout" || addquirks == "Loadout Only") + if(dresscode == "Naked") + player_as_human.equip_outfit_and_loadout(new /datum/outfit(), user.client?.prefs) + else + player_as_human.equip_outfit_and_loadout(dresscode, user.client?.prefs) + else if(dresscode != "Naked") + spawned_player.equipOutfit(dresscode) + if(addquirks == "Quirks & Loadout" || addquirks == "Quirks Only") + SSquirks.AssignQuirks(player_as_human, user.client) + player_as_human.dna.update_dna_identity() + else if(dresscode != "Naked") + spawned_player.equipOutfit(dresscode) + QDEL_IN(user, 1) + + if (teleport_option == "Bluespace") + playsound(spawned_player, 'sound/magic/Disable_Tech.ogg', 100, 1) + + if(user.mind && isliving(spawned_player)) + user.mind.transfer_to(spawned_player, 1) // second argument to force key move to new mob + else + spawned_player.ckey = user.key + + if(give_return != "No") + var/datum/action/cooldown/spell/return_back/return_spell = new(spawned_player) + return_spell.Grant(spawned_player) + + switch(teleport_option) + if("Bluespace") + spawned_player.forceMove(current_turf) + + var/datum/effect_system/spark_spread/quantum/sparks = new + sparks.set_up(10, 1, spawned_player) + sparks.attach(get_turf(spawned_player)) + sparks.start() + if("Pod") + var/obj/structure/closet/supplypod/empty_pod = new() + + empty_pod.style = /datum/pod_style/advanced + empty_pod.bluespace = TRUE + empty_pod.explosionSize = list(0,0,0,0) + empty_pod.desc = "A sleek, and slightly worn bluespace pod - its probably seen many deliveries..." + + spawned_player.forceMove(empty_pod) + + new /obj/effect/pod_landingzone(current_turf, empty_pod) + +/client/proc/robust_dress_shop_skyrat() + var/list/baseoutfits = list("Naked","Custom","As Job...", "As Plasmaman...") + var/list/outfits = list() + var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - typesof(/datum/outfit/plasmaman) + + for(var/path in paths) + // Get the datum from the path so we can grab its name. + var/datum/outfit/path_as_outfit = path + outfits[initial(path_as_outfit.name)] = path + + var/dresscode = tgui_input_list(src, "Select outfit", "Robust quick dress shop", baseoutfits + sort_list(outfits)) + + if (isnull(dresscode)) + return + + if (outfits[dresscode]) + dresscode = outfits[dresscode] + + if (dresscode == "As Job...") + var/list/job_paths = subtypesof(/datum/outfit/job) + var/list/job_outfits = list() + for(var/path in job_paths) + var/datum/outfit/O = path + job_outfits[initial(O.name)] = path + + dresscode = input("Select job equipment", "Robust quick dress shop") as null|anything in sort_list(job_outfits) + dresscode = job_outfits[dresscode] + if(isnull(dresscode)) + return + + if (dresscode == "As Plasmaman...") + var/list/plasmaman_paths = typesof(/datum/outfit/plasmaman) + var/list/plasmaman_outfits = list() + for(var/path in plasmaman_paths) + var/datum/outfit/O = path + plasmaman_outfits[initial(O.name)] = path + + dresscode = input("Select plasmeme equipment", "Robust quick dress shop") as null|anything in sort_list(plasmaman_outfits) + dresscode = plasmaman_outfits[dresscode] + if(isnull(dresscode)) + return + + if (dresscode == "Custom") + var/list/custom_names = list() + for(var/datum/outfit/req_outfit in GLOB.custom_outfits) + custom_names[req_outfit.name] = req_outfit + var/selected_name = input("Select outfit", "Robust quick dress shop") as null|anything in sort_list(custom_names) + dresscode = custom_names[selected_name] + if(isnull(dresscode)) + return + + return dresscode diff --git a/modular_doppler/icspawn/spell.dm b/modular_doppler/icspawn/spell.dm new file mode 100644 index 0000000000000..b9dca8dbdb13d --- /dev/null +++ b/modular_doppler/icspawn/spell.dm @@ -0,0 +1,35 @@ +/datum/action/cooldown/spell/return_back + name = "Return" + desc = "Activates your return beacon." + sound = 'sound/magic/Repulse.ogg' + button_icon_state = "lightning" + spell_requirements = NONE + invocation = "Return on!" + invocation_type = INVOCATION_WHISPER + school = SCHOOL_EVOCATION + + +/datum/action/cooldown/spell/return_back/can_cast_spell(feedback) + return TRUE + + +/datum/action/cooldown/spell/return_back/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/user = cast_on + if(!istype(cast_on)) + return + + var/mob/dead/observer/ghost = user.ghostize(FALSE) + + var/datum/effect_system/spark_spread/quantum/sparks = new + sparks.set_up(10, 1, user) + sparks.attach(user.loc) + sparks.start() + + qdel(user) + + + // Get them back to their regular name. + ghost.set_ghost_appearance() + if(ghost.client && ghost.client.prefs) + ghost.deadchat_name = ghost.client.prefs?.read_preference(/datum/preference/name/real_name) diff --git a/modular_doppler/modular_cosmetics/GAGS/greyscale_configs_neck.dm b/modular_doppler/modular_cosmetics/GAGS/greyscale_configs_neck.dm new file mode 100644 index 0000000000000..9b207650ddcf9 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/greyscale_configs_neck.dm @@ -0,0 +1,101 @@ +/* + />  フ meow! this dm file contains greyscale configs for any clothes we add. please name + |  _ _| / them in obvious, easy to search ways that are consistent with your item paths <3 + /` ミ_xノ + /     | + /  ヽ   ノ + │  | | | +/ ̄|   | | | +( ̄ヽ__ヽ_)__) +\二) +*/ + + +/////////// +//COLLARS// +/////////// +/datum/greyscale_config/collar + name = "Collar" + icon_file = 'modular_doppler/modular_cosmetics/GAGS/icons/collar.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell.json' + +// Bell +/datum/greyscale_config/collar/bell + name = "Bell Collar" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell.json' + +/datum/greyscale_config/collar/bell/worn + name = "Bell Collar (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell_worn.json' + +// Choker +/datum/greyscale_config/collar/choker + name = "Choker" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker.json' + +/datum/greyscale_config/collar/choker/worn + name = "Choker (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker_worn.json' + +// Thin Choker +/datum/greyscale_config/collar/thinchoker + name = "Thin Choker" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker.json' + +/datum/greyscale_config/collar/thinchoker/worn + name = "Thin Choker (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker_worn.json' + +// Cow +/datum/greyscale_config/collar/cow + name = "Cowbell Collar" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow.json' + +/datum/greyscale_config/collar/cow/worn + name = "Cowbell Collar (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow_worn.json' + +// Cross +/datum/greyscale_config/collar/cross + name = "Cross Collar" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross.json' + +/datum/greyscale_config/collar/cross/worn + name = "Cross Collar (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross_worn.json' + +// Holo +/datum/greyscale_config/collar/holo + name = "Holo Collar" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo.json' + +/datum/greyscale_config/collar/holo/worn + name = "Holo Collar (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo_worn.json' + +// Leather +/datum/greyscale_config/collar/leather + name = "Leather Collar" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather.json' + +/datum/greyscale_config/collar/leather/worn + name = "Leather Collar (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather_worn.json' + +// Pet +/datum/greyscale_config/collar/pet + name = "Pet Collar" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet.json' + +/datum/greyscale_config/collar/pet/worn + name = "Pet Collar (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet_worn.json' + +// Spiked +/datum/greyscale_config/collar/spike + name = "Spiked Collar" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike.json' + +/datum/greyscale_config/collar/spike/worn + name = "Spiked Collar (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike_worn.json' diff --git a/modular_doppler/modular_cosmetics/GAGS/icons/collar.dmi b/modular_doppler/modular_cosmetics/GAGS/icons/collar.dmi new file mode 100644 index 0000000000000..011280e8164ed Binary files /dev/null and b/modular_doppler/modular_cosmetics/GAGS/icons/collar.dmi differ diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell.json new file mode 100644 index 0000000000000..22c973072fd1d --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell.json @@ -0,0 +1,16 @@ +{ + "bell": [ + { + "type": "icon_state", + "icon_state": "collar_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bell_tag_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell_worn.json new file mode 100644 index 0000000000000..bd951dfcd28a3 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/bell_worn.json @@ -0,0 +1,16 @@ +{ + "bell": [ + { + "type": "icon_state", + "icon_state": "bell_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "bell_tag_mob", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker.json new file mode 100644 index 0000000000000..711f6e62213fd --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker.json @@ -0,0 +1,10 @@ +{ + "choker": [ + { + "type": "icon_state", + "icon_state": "choker_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker_worn.json new file mode 100644 index 0000000000000..5a54d5e06a132 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/choker_worn.json @@ -0,0 +1,10 @@ +{ + "choker": [ + { + "type": "icon_state", + "icon_state": "choker_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow.json new file mode 100644 index 0000000000000..89ec7c3038d5b --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow.json @@ -0,0 +1,16 @@ +{ + "cow": [ + { + "type": "icon_state", + "icon_state": "collar_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "cow_tag_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow_worn.json new file mode 100644 index 0000000000000..d3d88b49ad444 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cow_worn.json @@ -0,0 +1,16 @@ +{ + "cow": [ + { + "type": "icon_state", + "icon_state": "cow_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "cow_tag_mob", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross.json new file mode 100644 index 0000000000000..6266dc4774199 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross.json @@ -0,0 +1,16 @@ +{ + "cross": [ + { + "type": "icon_state", + "icon_state": "collar_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "cross_tag_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross_worn.json new file mode 100644 index 0000000000000..26b6c7147639d --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/cross_worn.json @@ -0,0 +1,16 @@ +{ + "cross": [ + { + "type": "icon_state", + "icon_state": "cross_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "cross_tag_mob", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo.json new file mode 100644 index 0000000000000..4747f1fec2ce4 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo.json @@ -0,0 +1,16 @@ +{ + "holo": [ + { + "type": "icon_state", + "icon_state": "collar_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "holo_tag_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo_worn.json new file mode 100644 index 0000000000000..7af595b1dc16f --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/holo_worn.json @@ -0,0 +1,16 @@ +{ + "holo": [ + { + "type": "icon_state", + "icon_state": "holo_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "holo_tag_mob", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather.json new file mode 100644 index 0000000000000..cea6800b3d69e --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather.json @@ -0,0 +1,22 @@ +{ + "leather": [ + { + "type": "icon_state", + "icon_state": "leather_collar_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "leather_stripe_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "leather_tag_obj", + "blend_mode": "overlay", + "color_ids": [ 3 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather_worn.json new file mode 100644 index 0000000000000..5018b2a41b3a3 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/leather_worn.json @@ -0,0 +1,22 @@ +{ + "leather": [ + { + "type": "icon_state", + "icon_state": "leather_collar_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "leather_stripe_mob", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "leather_tag_mob", + "blend_mode": "overlay", + "color_ids": [ 3 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet.json new file mode 100644 index 0000000000000..4c8668538b74c --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet.json @@ -0,0 +1,16 @@ +{ + "pet": [ + { + "type": "icon_state", + "icon_state": "pet_collar_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "pet_tag_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet_worn.json new file mode 100644 index 0000000000000..2e068eecf2bfb --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/pet_worn.json @@ -0,0 +1,16 @@ +{ + "pet": [ + { + "type": "icon_state", + "icon_state": "pet_collar_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "pet_tag_mob", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike.json new file mode 100644 index 0000000000000..09fc177ce4919 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike.json @@ -0,0 +1,16 @@ +{ + "spike": [ + { + "type": "icon_state", + "icon_state": "spike_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "spike_tag_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike_worn.json new file mode 100644 index 0000000000000..19a8a32a6a949 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/spike_worn.json @@ -0,0 +1,16 @@ +{ + "spike": [ + { + "type": "icon_state", + "icon_state": "spike_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "spike_tag_mob", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker.json new file mode 100644 index 0000000000000..9e7776e7813a2 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker.json @@ -0,0 +1,10 @@ +{ + "thinchoker": [ + { + "type": "icon_state", + "icon_state": "thinchoker_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker_worn.json new file mode 100644 index 0000000000000..ef7740874964d --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/collar/thinchoker_worn.json @@ -0,0 +1,10 @@ +{ + "thinchoker": [ + { + "type": "icon_state", + "icon_state": "thinchoker_mob", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/code/neck/collar.dm b/modular_doppler/modular_cosmetics/code/neck/collar.dm new file mode 100644 index 0000000000000..a69b9cbb8ce64 --- /dev/null +++ b/modular_doppler/modular_cosmetics/code/neck/collar.dm @@ -0,0 +1,144 @@ +/obj/item/clothing/neck/human_petcollar + name = "pet collar" + desc = "It's for pets. Though you probably could wear it yourself, you'd doubtless be the subject of ridicule." + icon_state = "pet" + greyscale_config = /datum/greyscale_config/collar/pet + greyscale_config_worn = /datum/greyscale_config/collar/pet/worn + greyscale_colors = "#44BBEE#FFCC00" + obj_flags = parent_type::obj_flags | UNIQUE_RENAME + flags_1 = IS_PLAYER_COLORABLE_1 + alternate_worn_layer = UNDER_SUIT_LAYER + /// What treat item spawns inside the collar? + var/treat_path = /obj/item/food/cookie + +/obj/item/clothing/neck/human_petcollar/Initialize(mapload) + . = ..() + create_storage(storage_type = /datum/storage/pockets/small) + atom_storage.set_holdable(list( + /obj/item/food/cookie, + /obj/item/key/collar, + )) + if(treat_path) + new treat_path(src) + +// incompatible storage by default stops attack chain, but this does not, allows pen renaming +/obj/item/clothing/neck/human_petcollar/storage_insert_on_interacted_with(datum/storage/storage, obj/item/inserted, mob/living/user) + return is_type_in_typecache(inserted, storage.can_hold) + +/obj/item/clothing/neck/human_petcollar/leather + name = "leather pet collar" + icon_state = "leather" + greyscale_config = /datum/greyscale_config/collar/leather + greyscale_config_worn = /datum/greyscale_config/collar/leather/worn + greyscale_colors = "#222222#888888#888888" + +/obj/item/clothing/neck/human_petcollar/choker + name = "choker" + desc = "Quite fashionable... if you're somebody who's just read their first BDSM-themed erotica novel." + icon_state = "choker" + greyscale_config = /datum/greyscale_config/collar/choker + greyscale_config_worn = /datum/greyscale_config/collar/choker/worn + greyscale_colors = "#222222" + +/obj/item/clothing/neck/human_petcollar/thinchoker + name = "thin choker" + desc = "Like the normal one, but thinner!" + icon_state = "thinchoker" + greyscale_config = /datum/greyscale_config/collar/thinchoker + greyscale_config_worn = /datum/greyscale_config/collar/thinchoker/worn + greyscale_colors = "#222222" + +/obj/item/key/collar + name = "collar key" + desc = "A key for a tiny lock on a collar or bag." + obj_flags = parent_type::obj_flags | UNIQUE_RENAME + +/obj/item/clothing/neck/human_petcollar/locked + name = "locked collar" + desc = "A collar that has a small lock on it to keep it from being removed." + treat_path = /obj/item/key/collar + /// Is the collar currently locked? + var/locked = FALSE + +/obj/item/clothing/neck/human_petcollar/locked/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ITEM_PRE_UNEQUIP, PROC_REF(can_unequip)) + +/obj/item/clothing/neck/human_petcollar/locked/proc/can_unequip(obj/item/source, force, atom/newloc, no_move, invdrop, silent) + var/mob/living/carbon/wearer = source.loc + if(istype(wearer) && wearer.wear_neck == source && locked) + to_chat(wearer, "The collar is locked! You'll need to unlock it before you can take it off!") + return COMPONENT_ITEM_BLOCK_UNEQUIP + return NONE + +/obj/item/clothing/neck/human_petcollar/locked/canStrip(mob/stripper, mob/owner) + if(!locked) + return ..() + owner.balloon_alert(stripper, "locked!") + return FALSE + +/obj/item/clothing/neck/human_petcollar/locked/tool_act(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/key/collar)) + return ..() + to_chat(user, span_warning("With a click, the collar [locked ? "unlocks" : "locks"]!")) + locked = !locked + return ITEM_INTERACT_SUCCESS + +/obj/item/clothing/neck/human_petcollar/locked/examine(mob/user) + . = ..() + . += "It seems to be [locked ? "locked" : "unlocked"]." + +/obj/item/clothing/neck/human_petcollar/locked/bell + name = "bell collar" + desc = "A loud and annoying collar for your little kitten!" + icon_state = "bell" + greyscale_config = /datum/greyscale_config/collar/bell + greyscale_config_worn = /datum/greyscale_config/collar/bell/worn + greyscale_colors = "#222222#C0C0C0" + +/obj/item/clothing/neck/human_petcollar/locked/choker + name = "choker" + desc = "Quite fashionable... if you're somebody who's just read their first BDSM-themed erotica novel." + icon_state = "choker" + greyscale_config = /datum/greyscale_config/collar/choker + greyscale_config_worn = /datum/greyscale_config/collar/choker/worn + greyscale_colors = "#222222" + +/obj/item/clothing/neck/human_petcollar/locked/cow + name = "cowbell collar" + desc = "Don't fear the reaper, now your pet doesn't have to." + icon_state = "cow" + greyscale_config = /datum/greyscale_config/collar/cow + greyscale_config_worn = /datum/greyscale_config/collar/cow/worn + greyscale_colors = "#663300#FFCC00" + +/obj/item/clothing/neck/human_petcollar/locked/cross + name = "cross collar" + desc = "A religious punishment, probably." + icon_state = "cross" + greyscale_config = /datum/greyscale_config/collar/cross + greyscale_config_worn = /datum/greyscale_config/collar/cross/worn + greyscale_colors = "#663300#FFCC00" + +/obj/item/clothing/neck/human_petcollar/locked/holo + name = "holocollar" + desc = "A collar with holographic information. Like a microchip, but around the neck." + icon_state = "holo" + greyscale_config = /datum/greyscale_config/collar/holo + greyscale_config_worn = /datum/greyscale_config/collar/holo/worn + greyscale_colors = "#292929#3399FF" + +/obj/item/clothing/neck/human_petcollar/locked/leather + name = "leather pet collar" + icon_state = "leather" + greyscale_config = /datum/greyscale_config/collar/leather + greyscale_config_worn = /datum/greyscale_config/collar/leather/worn + greyscale_colors = "#222222#888888#888888" + +/obj/item/clothing/neck/human_petcollar/locked/spike + name = "spiked collar" + desc = "A collar for a moody pet. Or a pitbull." + icon_state = "spike" + greyscale_config = /datum/greyscale_config/collar/spike + greyscale_config_worn = /datum/greyscale_config/collar/spike/worn + greyscale_colors = "#292929#C0C0C0" diff --git a/tgstation.dme b/tgstation.dme index 50d463d3054ed..63c5afe24a51e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5995,7 +5995,7 @@ #include "code\modules\surgery\organs\autosurgeon.dm" #include "code\modules\surgery\organs\helpers.dm" #include "code\modules\surgery\organs\organ_movement.dm" -#include "code\modules\surgery\organs\external\_external_organ.dm" +#include "code\modules\surgery\organs\external\_visual_organs.dm" #include "code\modules\surgery\organs\external\restyling.dm" #include "code\modules\surgery\organs\external\spines.dm" #include "code\modules\surgery\organs\external\tails.dm" @@ -6361,9 +6361,14 @@ #include "modular_doppler\emotes\code\added_emotes\human_things.dm" #include "modular_doppler\emotes\code\added_emotes\robot_sounds.dm" #include "modular_doppler\face_mouse_preferences\code\face_mouse_pref.dm" +#include "modular_doppler\icspawn\cconsultant_items.dm" +#include "modular_doppler\icspawn\observer_spawn.dm" +#include "modular_doppler\icspawn\spell.dm" #include "modular_doppler\languages\language_datums.dm" #include "modular_doppler\modular_cosmetics\code\jacket_pockets.dm" +#include "modular_doppler\modular_cosmetics\code\neck\collar.dm" #include "modular_doppler\modular_cosmetics\code\suits\jacket.dm" +#include "modular_doppler\modular_cosmetics\GAGS\greyscale_configs_neck.dm" #include "modular_doppler\modular_food_drinks_and_chems\chemistry_reagents.dm" #include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\alcohol reagents.dm" #include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\drink_reagents.dm"