From 68b370be8a6895b1d6c4d9d6f2aee593443fb0b7 Mon Sep 17 00:00:00 2001 From: Mechoid Date: Sat, 7 Nov 2020 12:06:35 -0800 Subject: [PATCH] Fresh Meat, Leatherworking (#7629) * Make Butchering more modular. Don't tell the demon lords. * No guts no glory. That was far more work than expected to get organs to just process and work for butchering holy shit. * Chitin and hides oh my. * squeak. ..Why did this not fire on local? * Squelch. * Seven cubed livers, for the doctors in their sterile halls. * Changeling * Prep for Datums * Fix Broken Broken Guns * Redundancy * F l a g s --- code/__defines/misc.dm | 2 + code/game/machinery/washing_machine.dm | 15 +- .../objects/items/stacks/sheets/leather.dm | 116 ++++++----- code/game/objects/items/stacks/stack.dm | 24 +-- code/game/objects/structures/bonfire.dm | 10 + code/modules/food/kitchen/gibber.dm | 37 ++-- code/modules/food/kitchen/smartfridge.dm | 15 ++ code/modules/materials/material_recipes.dm | 21 ++ code/modules/materials/material_sheets.dm | 13 +- code/modules/materials/materials.dm | 22 +- code/modules/mob/living/butchering.dm | 66 ++++++ .../mob/living/carbon/carbon_defines.dm | 2 +- .../mob/living/carbon/human/human_damage.dm | 5 +- .../mob/living/carbon/human/human_defines.dm | 4 +- .../mob/living/carbon/human/human_organs.dm | 2 + code/modules/mob/living/living.dm | 16 ++ code/modules/mob/living/living_defines.dm | 3 +- code/modules/mob/living/organs.dm | 28 +++ .../mob/living/simple_mob/appearance.dm | 3 +- .../mob/living/simple_mob/butchering.dm | 8 + code/modules/mob/living/simple_mob/defense.dm | 6 +- .../mob/living/simple_mob/harvesting.dm | 2 +- code/modules/mob/living/simple_mob/life.dm | 10 +- .../mob/living/simple_mob/simple_mob.dm | 54 ++--- .../simple_mob/subtypes/animal/animal.dm | 15 +- .../animal/giant_spider/_giant_spider.dm | 4 + code/modules/organs/internal/augment.dm | 2 + code/modules/organs/internal/brain.dm | 16 +- code/modules/organs/internal/liver.dm | 2 +- .../organs/internal/robotic/robotic.dm | 1 + code/modules/organs/organ.dm | 189 +++++++++++++----- code/modules/organs/organ_external.dm | 28 +-- code/modules/organs/organ_icon.dm | 7 +- code/modules/organs/subtypes/machine.dm | 1 + code/modules/organs/subtypes/standard.dm | 17 +- html/changelogs/mechoid - butchery.yml | 37 ++++ icons/obj/butchery.dmi | Bin 0 -> 372 bytes icons/obj/stacks.dmi | Bin 50787 -> 52715 bytes polaris.dme | 3 + 39 files changed, 599 insertions(+), 207 deletions(-) create mode 100644 code/modules/mob/living/butchering.dm create mode 100644 code/modules/mob/living/organs.dm create mode 100644 code/modules/mob/living/simple_mob/butchering.dm create mode 100644 html/changelogs/mechoid - butchery.yml create mode 100644 icons/obj/butchery.dmi diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index b531b87871..3165621a5a 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -148,6 +148,8 @@ #define MAT_METALHYDROGEN "mhydrogen" #define MAT_OSMIUM "osmium" #define MAT_GRAPHITE "graphite" +#define MAT_LEATHER "leather" +#define MAT_CHITIN "chitin" #define SHARD_SHARD "shard" #define SHARD_SHRAPNEL "shrapnel" diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 845eff59c1..2401d20ab1 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -33,7 +33,7 @@ /obj/machinery/washing_machine/Initialize() . = ..() default_apply_parts() - + /obj/machinery/washing_machine/AltClick() start() @@ -68,9 +68,14 @@ //Tanning! for(var/obj/item/stack/hairlesshide/HH in washing) - var/obj/item/stack/wetleather/WL = new(src) - WL.amount = HH.amount - qdel(HH) + var/obj/item/stack/WL = new HH.wet_type(src) + if(istype(WL)) + WL.amount = HH.amount + washing -= HH + HH.forceMove(get_turf(src)) + HH.use(HH.amount) + + washing += WL if(locate(/mob,washing)) state = 7 @@ -126,7 +131,7 @@ to_chat(user, "You can't fit \the [W] inside.") return - else if(istype(W, /obj/item/clothing) || istype(W, /obj/item/weapon/bedsheet)) + else if(istype(W, /obj/item/clothing) || istype(W, /obj/item/weapon/bedsheet) || istype(W, /obj/item/stack/hairlesshide)) if(washing.len < 5) if(state in list(1, 3)) user.drop_item() diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index 8bf366268a..54bc1e2fce 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -1,70 +1,75 @@ +/obj/item/stack/animalhide + name = "hide" + desc = "The hide of some creature." + icon_state = "sheet-hide" + drop_sound = 'sound/items/drop/cloth.ogg' + pickup_sound = 'sound/items/pickup/cloth.ogg' + amount = 1 + stacktype = "hide" + no_variants = TRUE + + var/process_type = /obj/item/stack/hairlesshide + /obj/item/stack/animalhide/human - name = "human skin" - desc = "The by-product of human farming." - singular_name = "human skin piece" + name = "skin" + desc = "The by-product of sapient farming." + singular_name = "skin piece" icon_state = "sheet-hide" no_variants = FALSE drop_sound = 'sound/items/drop/leather.ogg' pickup_sound = 'sound/items/pickup/leather.ogg' - -/obj/item/stack/animalhide/human - amount = 50 + amount = 1 + stacktype = "hide-human" /obj/item/stack/animalhide/corgi name = "corgi hide" desc = "The by-product of corgi farming." singular_name = "corgi hide piece" icon_state = "sheet-corgi" - -/obj/item/stack/animalhide/corgi - amount = 50 + amount = 1 + stacktype = "hide-corgi" /obj/item/stack/animalhide/cat name = "cat hide" desc = "The by-product of cat farming." singular_name = "cat hide piece" icon_state = "sheet-cat" - -/obj/item/stack/animalhide/cat - amount = 50 + amount = 1 + stacktype = "hide-cat" /obj/item/stack/animalhide/monkey name = "monkey hide" desc = "The by-product of monkey farming." singular_name = "monkey hide piece" icon_state = "sheet-monkey" - -/obj/item/stack/animalhide/monkey - amount = 50 + amount = 1 + stacktype = "hide-monkey" /obj/item/stack/animalhide/lizard name = "lizard skin" desc = "Sssssss..." singular_name = "lizard skin piece" icon_state = "sheet-lizard" - -/obj/item/stack/animalhide/lizard - amount = 50 + amount = 1 + stacktype = "hide-lizard" /obj/item/stack/animalhide/xeno name = "alien hide" desc = "The skin of a terrible creature." singular_name = "alien hide piece" icon_state = "sheet-xeno" - -/obj/item/stack/animalhide/xeno - amount = 50 + amount = 1 + stacktype = "hide-xeno" //don't see anywhere else to put these, maybe together they could be used to make the xenos suit? /obj/item/stack/xenochitin name = "alien chitin" desc = "A piece of the hide of a terrible creature." - singular_name = "alien hide piece" + singular_name = "alien chitin piece" icon = 'icons/mob/alien.dmi' icon_state = "chitin" - -/obj/item/stack/xenochitin - amount = 50 + amount = 1 + stacktype = "hide-chitin" /obj/item/xenos_claw name = "alien claw" @@ -84,9 +89,22 @@ singular_name = "hairless hide piece" icon_state = "sheet-hairlesshide" no_variants = FALSE + amount = 1 + stacktype = "hairlesshide" + var/cleaning = FALSE // Can we be water_acted, or are we busy? To prevent accidental hide duplication and the collapse of causality. -/obj/item/stack/hairlesshide - amount = 50 + var/wet_type = /obj/item/stack/wetleather + +/obj/item/stack/hairlesshide/water_act(var/wateramount) + ..() + cleaning = TRUE + while(amount > 0 && wateramount > 0) + use(1) + wateramount-- + new wet_type(get_turf(src)) + cleaning = FALSE + + return /obj/item/stack/wetleather name = "wet leather" @@ -96,29 +114,28 @@ var/wetness = 30 //Reduced when exposed to high temperautres var/drying_threshold_temperature = 500 //Kelvin to start drying no_variants = FALSE + amount = 1 + stacktype = "wetleather" -/obj/item/stack/wetleather - amount = 50 + var/dry_type = /obj/item/stack/material/leather //Step one - dehairing. /obj/item/stack/animalhide/attackby(obj/item/weapon/W as obj, mob/user as mob) - if( istype(W, /obj/item/weapon/material/knife) || \ - istype(W, /obj/item/weapon/material/twohanded/fireaxe) || \ - istype(W, /obj/item/weapon/material/knife/machete/hatchet) ) - + if(has_edge(W) || is_sharp(W)) //visible message on mobs is defined as visible_message(var/message, var/self_message, var/blind_message) usr.visible_message("\The [usr] starts cutting hair off \the [src]", "You start cutting the hair off \the [src]", "You hear the sound of a knife rubbing against flesh") if(do_after(user,50)) to_chat(usr, "You cut the hair from this [src.singular_name]") //Try locating an exisitng stack on the tile and add to there if possible for(var/obj/item/stack/hairlesshide/HS in usr.loc) - if(HS.amount < 50) + if(HS.amount < 50 && istype(HS, process_type)) HS.amount++ src.use(1) - break + return //If it gets to here it means it did not find a suitable stack on the tile. - var/obj/item/stack/hairlesshide/HS = new(usr.loc) - HS.amount = 1 + var/obj/item/stack/HS = new process_type(usr.loc) + if(istype(HS)) + HS.amount = 1 src.use(1) else ..() @@ -132,15 +149,20 @@ if(exposed_temperature >= drying_threshold_temperature) wetness-- if(wetness == 0) - //Try locating an exisitng stack on the tile and add to there if possible - for(var/obj/item/stack/material/leather/HS in src.loc) - if(HS.amount < 50) - HS.amount++ - src.use(1) - wetness = initial(wetness) - break - //If it gets to here it means it did not find a suitable stack on the tile. - var/obj/item/stack/material/leather/HS = new(src.loc) - HS.amount = 1 + dry() + +/obj/item/stack/wetleather/proc/dry() + //Try locating an exisitng stack on the tile and add to there if possible + for(var/obj/item/stack/material/leather/HS in src.loc) + if(HS.amount < 50) + HS.amount++ wetness = initial(wetness) src.use(1) + return + //If it gets to here it means it did not find a suitable stack on the tile. + var/obj/item/stack/HS = new dry_type(src.loc) + + if(istype(HS)) + HS.amount = 1 + wetness = initial(wetness) + src.use(1) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index b75cb0c416..db7b4055b2 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -59,7 +59,7 @@ /obj/item/stack/examine(mob/user) . = ..() - + if(Adjacent(user)) if(!uses_charge) . += "There are [src.amount] [src.singular_name]\s in the stack." @@ -378,17 +378,17 @@ var/use_material var/pass_color - New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1, time = 0, one_per_turf = 0, on_floor = 0, supplied_material = null, pass_stack_color) - src.title = title - src.result_type = result_type - src.req_amount = req_amount - src.res_amount = res_amount - src.max_res_amount = max_res_amount - src.time = time - src.one_per_turf = one_per_turf - src.on_floor = on_floor - src.use_material = supplied_material - src.pass_color = pass_stack_color +/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1, time = 0, one_per_turf = 0, on_floor = 0, supplied_material = null, pass_stack_color) + src.title = title + src.result_type = result_type + src.req_amount = req_amount + src.res_amount = res_amount + src.max_res_amount = max_res_amount + src.time = time + src.one_per_turf = one_per_turf + src.on_floor = on_floor + src.use_material = supplied_material + src.pass_color = pass_stack_color /* * Recipe list datum diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm index af52c59b71..3da06fa226 100644 --- a/code/game/objects/structures/bonfire.dm +++ b/code/game/objects/structures/bonfire.dm @@ -240,6 +240,16 @@ removed.add_thermal_energy(heat_transfer) + for(var/mob/living/L in view(3, src)) + L.add_modifier(/datum/modifier/endothermic, 10 SECONDS, null, TRUE) + + for(var/obj/item/stack/wetleather/WL in view(2, src)) + if(WL.wetness >= 0) + WL.dry() + continue + + WL.wetness = max(0, WL.wetness - rand(1, 4)) + env.merge(removed) /obj/structure/bonfire/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) diff --git a/code/modules/food/kitchen/gibber.dm b/code/modules/food/kitchen/gibber.dm index 1fca845091..6402a432ae 100644 --- a/code/modules/food/kitchen/gibber.dm +++ b/code/modules/food/kitchen/gibber.dm @@ -188,18 +188,13 @@ update_icon() var/slab_name = occupant.name - var/slab_count = 3 - var/slab_type = /obj/item/weapon/reagent_containers/food/snacks/meat + var/slab_count = 2 + occupant.meat_amount + var/slab_type = occupant.meat_type ? occupant.meat_type : /obj/item/weapon/reagent_containers/food/snacks/meat var/slab_nutrition = src.occupant.nutrition / 15 - // Some mobs have specific meat item types. - if(istype(src.occupant,/mob/living/simple_mob)) - var/mob/living/simple_mob/critter = src.occupant - if(critter.meat_amount) - slab_count = critter.meat_amount - if(critter.meat_type) - slab_type = critter.meat_type - else if(istype(src.occupant,/mob/living/carbon/human)) + var/list/byproducts = occupant?.butchery_loot?.Copy() + + if(istype(src.occupant,/mob/living/carbon/human)) var/mob/living/carbon/human/H = occupant slab_name = src.occupant.real_name slab_type = H.isSynthetic() ? /obj/item/stack/material/steel : H.species.meat_type @@ -209,7 +204,8 @@ slab_nutrition *= 0.5 slab_nutrition /= slab_count - for(var/i=1 to slab_count) + while(slab_count) + slab_count-- var/obj/item/weapon/reagent_containers/food/snacks/meat/new_meat = new slab_type(src, rand(3,8)) if(istype(new_meat)) new_meat.name = "[slab_name] [new_meat.name]" @@ -222,17 +218,26 @@ src.occupant.ghostize() spawn(gib_time) - - operating = 0 occupant.gib() occupant = null - playsound(src, 'sound/effects/splat.ogg', 50, 1) operating = 0 + if(LAZYLEN(byproducts)) + for(var/path in byproducts) + while(byproducts[path]) + if(prob(min(90,30 * byproducts[path]))) + new path(src) + + byproducts[path] -= 1 + for (var/obj/thing in contents) - // There's a chance that the gibber will fail to destroy some evidence. + // There's a chance that the gibber will fail to destroy or butcher some evidence. if(istype(thing,/obj/item/organ) && prob(80)) - qdel(thing) + var/obj/item/organ/OR = thing + if(OR.can_butcher(src)) + OR.butcher(src, null, src) // Butcher it, and add it to our list of things to launch. + else + qdel(thing) continue thing.forceMove(get_turf(thing)) // Drop it onto the turf for throwing. thing.throw_at(get_edge_target_turf(src,gib_throw_dir),rand(0,3),emagged ? 100 : 50) // Being pelted with bits of meat and bone would hurt. diff --git a/code/modules/food/kitchen/smartfridge.dm b/code/modules/food/kitchen/smartfridge.dm index e527df91b9..03a1c33bef 100644 --- a/code/modules/food/kitchen/smartfridge.dm +++ b/code/modules/food/kitchen/smartfridge.dm @@ -136,6 +136,10 @@ var/obj/item/weapon/reagent_containers/food/snacks/S = O if (S.dried_type) return 1 + + if(istype(O, /obj/item/stack/wetleather)) + return 1 + return 0 /obj/machinery/smartfridge/drying_rack/process() @@ -179,6 +183,17 @@ new D(get_turf(src)) qdel(S) return + + for(var/obj/item/stack/wetleather/WL in I.instances) + if(!WL.wetness) + if(WL.amount == 1) + WL.forceMove(get_turf(src)) + I.instances -= WL + WL.dry() + break + + WL.wetness = max(0, WL.wetness - rand(1, 3)) + return /obj/machinery/smartfridge/process() diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm index d55376bc65..31b0b51189 100644 --- a/code/modules/materials/material_recipes.dm +++ b/code/modules/materials/material_recipes.dm @@ -246,3 +246,24 @@ recipes += new/datum/stack_recipe("[display_name] net", /obj/item/weapon/material/fishing_net, 10, time = 5 SECONDS, supplied_material = "[name]", pass_stack_color = TRUE) recipes += new/datum/stack_recipe("[display_name] membrane", /obj/effect/alien/resin/membrane, 1, time = 2 SECONDS, pass_stack_color = TRUE) recipes += new/datum/stack_recipe("[display_name] node", /obj/effect/alien/weeds/node, 1, time = 4 SECONDS) + +/datum/material/leather/generate_recipes() + recipes = list() + recipes += new/datum/stack_recipe("bedsheet", /obj/item/weapon/bedsheet, 10, time = 30 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("uniform", /obj/item/clothing/under/color/white, 8, time = 15 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("foot wraps", /obj/item/clothing/shoes/footwraps, 2, time = 5 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("gloves", /obj/item/clothing/gloves/white, 2, time = 5 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("wig", /obj/item/clothing/head/powdered_wig, 4, time = 10 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("philosopher's wig", /obj/item/clothing/head/philosopher_wig, 50, time = 2 MINUTES, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("taqiyah", /obj/item/clothing/head/taqiyah, 3, time = 6 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("turban", /obj/item/clothing/head/turban, 3, time = 6 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("hijab", /obj/item/clothing/head/hijab, 3, time = 6 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("kippa", /obj/item/clothing/head/kippa, 3, time = 6 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("scarf", /obj/item/clothing/accessory/scarf/white, 4, time = 5 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("baggy pants", /obj/item/clothing/under/pants/baggy/white, 8, time = 10 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("belt pouch", /obj/item/weapon/storage/belt/fannypack/white, 25, time = 1 MINUTE, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("crude [display_name] bandage", /obj/item/stack/medical/crude_pack, 1, time = 2 SECONDS, pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("[display_name] net", /obj/item/weapon/material/fishing_net, 10, time = 5 SECONDS, supplied_material = "[name]", pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("[display_name] ring", /obj/item/clothing/gloves/ring/material, 1, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("[display_name] bracelet", /obj/item/clothing/accessory/bracelet/material, 1, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE) + recipes += new/datum/stack_recipe("[display_name] armor plate", /obj/item/weapon/material/armor_plating, 1, time = 20, on_floor = 1, supplied_material = "[name]", pass_stack_color = TRUE) diff --git a/code/modules/materials/material_sheets.dm b/code/modules/materials/material_sheets.dm index 36900f4c4f..a42e8a2e53 100644 --- a/code/modules/materials/material_sheets.dm +++ b/code/modules/materials/material_sheets.dm @@ -432,7 +432,18 @@ name = "leather" desc = "The by-product of mob grinding." icon_state = "sheet-leather" - default_type = "leather" + default_type = MAT_LEATHER + no_variants = FALSE + pass_color = TRUE + strict_color_stacking = TRUE + drop_sound = 'sound/items/drop/leather.ogg' + pickup_sound = 'sound/items/pickup/leather.ogg' + +/obj/item/stack/material/chitin + name = "chitin" + desc = "The by-product of mob grinding." + icon_state = "chitin" + default_type = MAT_CHITIN no_variants = FALSE pass_color = TRUE strict_color_stacking = TRUE diff --git a/code/modules/materials/materials.dm b/code/modules/materials/materials.dm index b317b8baad..3fd7d14f57 100644 --- a/code/modules/materials/materials.dm +++ b/code/modules/materials/materials.dm @@ -922,6 +922,7 @@ var/list/name_to_material sheet_singular_name = null sheet_plural_name = "pile" pass_stack_colors = TRUE + supply_conversion_value = 1 /datum/material/wood/log/sif name = MAT_SIFLOG @@ -1007,6 +1008,7 @@ var/list/name_to_material flags = MATERIAL_PADDING conductive = 0 pass_stack_colors = TRUE + supply_conversion_value = 2 /datum/material/cult name = "cult" @@ -1032,16 +1034,32 @@ var/list/name_to_material /datum/material/cult/reinf/place_dismantled_product(var/turf/target) new /obj/effect/decal/remains/human(target) +/datum/material/chitin + name = MAT_CHITIN + icon_colour = "#8d6653" + stack_type = /obj/item/stack/material/chitin + stack_origin_tech = list(TECH_MATERIAL = 3, TECH_BIO = 4) + icon_base = "solid" + icon_reinf = "reinf_mesh" + integrity = 60 + ignition_point = T0C+400 + melting_point = T0C+500 + protectiveness = 25 + conductive = 0 + supply_conversion_value = 4 + //TODO PLACEHOLDERS: /datum/material/leather - name = "leather" + name = MAT_LEATHER icon_colour = "#5C4831" - stack_origin_tech = list(TECH_MATERIAL = 2) + stack_type = /obj/item/stack/material/leather + stack_origin_tech = list(TECH_MATERIAL = 2, TECH_BIO = 2) flags = MATERIAL_PADDING ignition_point = T0C+300 melting_point = T0C+300 protectiveness = 3 // 13% conductive = 0 + supply_conversion_value = 3 /datum/material/carpet name = "carpet" diff --git a/code/modules/mob/living/butchering.dm b/code/modules/mob/living/butchering.dm new file mode 100644 index 0000000000..514475e2a2 --- /dev/null +++ b/code/modules/mob/living/butchering.dm @@ -0,0 +1,66 @@ + + +/mob/living + var/meat_amount = 0 // How much meat to drop from this mob when butchered + var/obj/meat_type // The meat object to drop + + var/gib_on_butchery = FALSE + + var/list/butchery_loot // Associated list, path = number. + +// Harvest an animal's delicious byproducts +/mob/living/proc/harvest(var/mob/user, var/obj/item/I) + if(meat_type && meat_amount>0 && (stat == DEAD)) + while(meat_amount > 0 && do_after(user, 0.5 SECONDS * (mob_size / 10), src)) + var/obj/item/meat = new meat_type(get_turf(src)) + meat.name = "[src.name] [meat.name]" + new /obj/effect/decal/cleanable/blood/splatter(get_turf(src)) + meat_amount-- + + if(!meat_amount) + handle_butcher(user, I) + +/mob/living/proc/can_butcher(var/mob/user, var/obj/item/I) // Override for special butchering checks. + if(((meat_type && meat_amount) || LAZYLEN(butchery_loot)) && stat == DEAD) + return TRUE + + return FALSE + +/mob/living/proc/handle_butcher(var/mob/user, var/obj/item/I) + if(!user || do_after(user, 2 SECONDS * mob_size / 10, src)) + if(LAZYLEN(butchery_loot)) + if(LAZYLEN(butchery_loot)) + for(var/path in butchery_loot) + while(butchery_loot[path]) + butchery_loot[path] -= 1 + var/obj/item/loot = new path(get_turf(src)) + loot.pixel_x = rand(-12, 12) + loot.pixel_y = rand(-12, 12) + + butchery_loot.Cut() + butchery_loot = null + + if(LAZYLEN(organs)) + organs_by_name.Cut() + + for(var/obj/item/organ/OR in organs) + OR.removed() + organs -= OR + + if(LAZYLEN(internal_organs)) + internal_organs_by_name.Cut() + + for(var/obj/item/organ/OR in internal_organs) + OR.removed() + internal_organs -= OR + + if(!ckey) + if(issmall(src)) + user?.visible_message("[user] chops up \the [src]!") + new /obj/effect/decal/cleanable/blood/splatter(get_turf(src)) + if(gib_on_butchery) + qdel(src) + else + user?.visible_message("[user] butchers \the [src] messily!") + if(gib_on_butchery) + gib() diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 1112b4e49c..07cd72fce9 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/ +/mob/living/carbon gender = MALE var/datum/species/species //Contains icon generation and language information, set during New(). var/list/stomach_contents = list() diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm index 3f256fbde2..013c033482 100644 --- a/code/modules/mob/living/carbon/human/human_damage.dm +++ b/code/modules/mob/living/carbon/human/human_damage.dm @@ -284,7 +284,7 @@ oxyloss = 0 else ..() - + /mob/living/carbon/human/adjustHalLoss(var/amount) if(species.flags & NO_PAIN) halloss = 0 @@ -435,13 +435,14 @@ This function restores all organs. return 0 return - +/* /mob/living/carbon/human/proc/get_organ(var/zone) if(!zone) zone = BP_TORSO else if (zone in list( O_EYES, O_MOUTH )) zone = BP_HEAD return organs_by_name[zone] +*/ /mob/living/carbon/human/apply_damage(var/damage = 0, var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/soaked = 0, var/sharp = 0, var/edge = 0, var/obj/used_weapon = null) if(Debug2) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 6e73ccd4bc..2f1bf6cd4d 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -43,7 +43,7 @@ var/age = 30 //Player's age (pure fluff) var/b_type = "A+" //Player's bloodtype - var/synthetic //If they are a synthetic (aka synthetic torso) + var/datum/robolimb/synthetic //If they are a synthetic (aka synthetic torso). Also holds the datum for the type of robolimb. var/list/all_underwear = list() var/list/all_underwear_metadata = list() @@ -84,7 +84,6 @@ var/special_voice = "" // For changing our voice. Used by a symptom. var/last_dam = -1 //Used for determining if we need to process all organs or just some or even none. - var/list/bad_external_organs = list()// organs we check until they are good. var/xylophone = 0 //For the spoooooooky xylophone cooldown @@ -115,3 +114,4 @@ var/mob/living/carbon/human/vr_link = null var/obj/machinery/machine_visual //machine that is currently applying visual effects to this mob. Only used for camera monitors currently. + butchery_loot = list(/obj/item/stack/animalhide/human = 1) \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/human_organs.dm b/code/modules/mob/living/carbon/human/human_organs.dm index 6ea0687d4d..2e2f04622b 100644 --- a/code/modules/mob/living/carbon/human/human_organs.dm +++ b/code/modules/mob/living/carbon/human/human_organs.dm @@ -5,6 +5,7 @@ update_icons_body() //Body handles eyes update_eyes() //For floating eyes only +/* /mob/living/carbon/var/list/internal_organs = list() /mob/living/carbon/human/var/list/organs = list() /mob/living/carbon/human/var/list/organs_by_name = list() // map organ names to organs @@ -13,6 +14,7 @@ /mob/living/carbon/human/proc/get_bodypart_name(var/zone) var/obj/item/organ/external/E = get_organ(zone) if(E) . = E.name +*/ /mob/living/carbon/human/proc/recheck_bad_external_organs() var/damage_this_tick = getToxLoss() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index aae4fe9737..b01c365887 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -31,7 +31,23 @@ nest = null if(buckled) buckled.unbuckle_mob(src, TRUE) + qdel(selected_image) + + if(LAZYLEN(organs)) + organs_by_name.Cut() + while(organs.len) + var/obj/item/OR = organs[1] + organs -= OR + qdel(OR) + + if(LAZYLEN(internal_organs)) + internal_organs_by_name.Cut() + while(internal_organs.len) + var/obj/item/OR = internal_organs[1] + internal_organs -= OR + qdel(OR) + return ..() //mob verbs are faster than object verbs. See mob/verb/examine. diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 92416a25f3..15b747c4be 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -70,5 +70,4 @@ var/makes_dirt = TRUE //FALSE if the mob shouldn't be making dirt on the ground when it walks var/looking_elsewhere = FALSE //If the mob's view has been relocated to somewhere else, like via a camera or with binocs - - var/image/selected_image = null // Used for buildmode AI control stuff. \ No newline at end of file + var/image/selected_image = null // Used for buildmode AI control stuff. diff --git a/code/modules/mob/living/organs.dm b/code/modules/mob/living/organs.dm new file mode 100644 index 0000000000..d0489bcf6f --- /dev/null +++ b/code/modules/mob/living/organs.dm @@ -0,0 +1,28 @@ +/mob/living + var/list/internal_organs = list() + var/list/organs = list() + var/list/organs_by_name = list() // map organ names to organs + var/list/internal_organs_by_name = list() // so internal organs have less ickiness too + var/list/bad_external_organs = list()// organs we check until they are good. + +/mob/living/proc/get_bodypart_name(var/zone) + var/obj/item/organ/external/E = get_organ(zone) + if(E) . = E.name + +/mob/living/proc/get_organ(var/zone) + if(!zone) + zone = BP_TORSO + else if (zone in list( O_EYES, O_MOUTH )) + zone = BP_HEAD + return organs_by_name[zone] + +/mob/living/gib() + for(var/obj/item/organ/I in internal_organs) + I.removed() + if(isturf(I?.loc)) // Some organs qdel themselves or other things when removed + I.throw_at(get_edge_target_turf(src,pick(alldirs)),rand(1,3),30) + + for(var/obj/item/organ/external/E in src.organs) + E.droplimb(0,DROPLIMB_EDGE,1) + + ..() diff --git a/code/modules/mob/living/simple_mob/appearance.dm b/code/modules/mob/living/simple_mob/appearance.dm index 05f758d7c4..e99cf40b63 100644 --- a/code/modules/mob/living/simple_mob/appearance.dm +++ b/code/modules/mob/living/simple_mob/appearance.dm @@ -71,6 +71,5 @@ /mob/living/simple_mob/proc/remove_eyes() cut_overlay(eye_layer) - /mob/living/simple_mob/gib() - ..(icon_gib,1,icon) // we need to specify where the gib animation is stored \ No newline at end of file + ..(icon_gib,1,icon) // we need to specify where the gib animation is stored diff --git a/code/modules/mob/living/simple_mob/butchering.dm b/code/modules/mob/living/simple_mob/butchering.dm new file mode 100644 index 0000000000..c03c6b8015 --- /dev/null +++ b/code/modules/mob/living/simple_mob/butchering.dm @@ -0,0 +1,8 @@ +/mob/living/simple_mob + gib_on_butchery = TRUE + +/mob/living/simple_mob/can_butcher(var/mob/user, var/obj/item/I) // Override for special butchering checks. + . = ..() + + if(. && (!is_sharp(I) || !has_edge(I))) + return FALSE diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm index 1a301b8bfd..b17274bd2f 100644 --- a/code/modules/mob/living/simple_mob/defense.dm +++ b/code/modules/mob/living/simple_mob/defense.dm @@ -68,9 +68,9 @@ else var/datum/gender/T = gender_datums[src.get_visible_gender()] to_chat(user, "\The [src] is dead, medical items won't bring [T.him] back to life.") // the gender lookup is somewhat overkill, but it functions identically to the obsolete gender macros and future-proofs this code - if(meat_type && (stat == DEAD)) //if the animal has a meat, and if it is dead. - if(istype(O, /obj/item/weapon/material/knife)) - harvest(user) + if(can_butcher(user, O)) //if the animal can be butchered, do so and return. It's likely to be gibbed. + harvest(user, O) + return if(user.a_intent == I_HELP && harvest_tool && istype(O, harvest_tool) && stat != DEAD) if(world.time > (harvest_recent + harvest_cooldown)) diff --git a/code/modules/mob/living/simple_mob/harvesting.dm b/code/modules/mob/living/simple_mob/harvesting.dm index 95de747fee..e9439fab99 100644 --- a/code/modules/mob/living/simple_mob/harvesting.dm +++ b/code/modules/mob/living/simple_mob/harvesting.dm @@ -17,7 +17,7 @@ /mob/living/simple_mob/examine(mob/user) . = ..() - if(user && harvest_tool && (get_dist(user, src) <= 3)) + if(stat != DEAD && user && harvest_tool && (get_dist(user, src) <= 3)) . += "\The [src] can be [harvest_verb] with a [initial(harvest_tool.name)] every [round(harvest_cooldown, 0.1)] minutes." var/time_to_harvest = (harvest_recent + harvest_cooldown) - world.time if(time_to_harvest > 0) diff --git a/code/modules/mob/living/simple_mob/life.dm b/code/modules/mob/living/simple_mob/life.dm index 88b7e1f332..cb973ce16d 100644 --- a/code/modules/mob/living/simple_mob/life.dm +++ b/code/modules/mob/living/simple_mob/life.dm @@ -14,6 +14,8 @@ handle_special() + handle_guts() + return TRUE @@ -94,7 +96,7 @@ throw_alert("oxy", /obj/screen/alert/too_much_oxy) else clear_alert("oxy") - + if(min_tox && Environment.gas["phoron"] < min_tox) atmos_unsuitable = 2 throw_alert("tox_in_air", /obj/screen/alert/not_enough_tox) @@ -137,6 +139,12 @@ else adjustOxyLoss(-unsuitable_atoms_damage) +/mob/living/simple_mob/proc/handle_guts() + for(var/obj/item/organ/OR in internal_organs) + OR.process() + + for(var/obj/item/organ/OR in organs) + OR.process() /mob/living/simple_mob/proc/handle_supernatural() if(purge) diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm index 1248602e3f..e309ce914b 100644 --- a/code/modules/mob/living/simple_mob/simple_mob.dm +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -57,8 +57,6 @@ var/response_harm = "tries to hurt" // If clicked on harm intent var/list/friends = list() // Mobs on this list wont get attacked regardless of faction status. var/harm_intent_damage = 3 // How much an unarmed harm click does to this mob. - var/meat_amount = 0 // How much meat to drop from this mob when butchered - var/obj/meat_type // The meat object to drop var/list/loot_list = list() // The list of lootable objects to drop, with "/path = prob%" structure var/obj/item/weapon/card/id/myid// An ID card if they have one to give them access to stuff. @@ -156,6 +154,11 @@ // contained in a cage var/in_stasis = 0 + // Used for if the mob can drop limbs. Overrides species dmi. + var/limb_icon + // Used for if the mob can drop limbs. Overrides the icon cache key, so it doesn't keep remaking the icon needlessly. + var/limb_icon_key + /mob/living/simple_mob/Initialize() verbs -= /mob/verb/observe health = maxHealth @@ -167,8 +170,31 @@ if(has_eye_glow) add_eyes() - return ..() + if(LAZYLEN(organs)) + for(var/path in organs) + if(ispath(path)) + var/obj/item/organ/external/neworg = new path(src) + neworg.name = "[name] [neworg.name]" + neworg.meat_type = meat_type + + if(limb_icon) + neworg.force_icon = limb_icon + neworg.force_icon_key = limb_icon_key + + organs |= neworg + organs -= path + + if(LAZYLEN(internal_organs)) + for(var/path in internal_organs) + if(ispath(path)) + var/obj/item/organ/neworg = new path(src) + neworg.name = "[name] [neworg.name]" + neworg.meat_type = meat_type + internal_organs |= neworg + internal_organs -= path + + return ..() /mob/living/simple_mob/Destroy() default_language = null @@ -187,7 +213,6 @@ update_icon() ..() - //Client attached /mob/living/simple_mob/Login() . = ..() @@ -266,27 +291,6 @@ /mob/living/simple_mob/get_speech_ending(verb, var/ending) return verb - -// Harvest an animal's delicious byproducts -/mob/living/simple_mob/proc/harvest(var/mob/user, var/invisible) - var/actual_meat_amount = max(1,(meat_amount/2)) - var/attacker_name = user.name - if(invisible) - attacker_name = "someone" - - if(meat_type && actual_meat_amount>0 && (stat == DEAD)) - for(var/i=0;i[attacker_name] chops up \the [src]!") - new/obj/effect/decal/cleanable/blood/splatter(get_turf(src)) - qdel(src) - else - user.visible_message("[attacker_name] butchers \the [src] messily!") - gib() - - /mob/living/simple_mob/is_sentient() return mob_class & MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL|MOB_CLASS_SLIME // Update this if needed. diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm index e41d5ae66b..56194f13bb 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm @@ -6,4 +6,17 @@ response_disarm = "shoos" response_harm = "hits" - ai_holder_type = /datum/ai_holder/simple_mob/melee \ No newline at end of file + ai_holder_type = /datum/ai_holder/simple_mob/melee + + internal_organs = list(\ + /obj/item/organ/internal/brain,\ + /obj/item/organ/internal/heart,\ + /obj/item/organ/internal/liver,\ + /obj/item/organ/internal/stomach,\ + /obj/item/organ/internal/intestine,\ + /obj/item/organ/internal/lungs\ + ) + + butchery_loot = list(\ + /obj/item/stack/animalhide = 3\ + ) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm index 3721168ff9..77ecf14da6 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm @@ -110,6 +110,10 @@ var/poison_chance = 10 // Chance for injection to occur. var/poison_per_bite = 5 // Amount added per injection. + butchery_loot = list(\ + /obj/item/stack/material/chitin = 1\ + ) + /mob/living/simple_mob/animal/giant_spider/apply_melee_effects(var/atom/A) if(isliving(A)) var/mob/living/L = A diff --git a/code/modules/organs/internal/augment.dm b/code/modules/organs/internal/augment.dm index 569b5dda44..da8ee8d577 100644 --- a/code/modules/organs/internal/augment.dm +++ b/code/modules/organs/internal/augment.dm @@ -14,6 +14,8 @@ target_parent_classes = list() // Is the parent supposed to be organic, robotic, assisted? forgiving_class = TRUE // Will the organ give its verbs when it isn't a perfect match? I.E., assisted in organic, synthetic in organic. + butcherable = FALSE + var/obj/item/integrated_object // Objects held by the organ, used for re-usable, deployable things. var/integrated_object_type // Object type the organ will spawn. var/target_slot = null diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm index d2b24dba00..f56c46fab5 100644 --- a/code/modules/organs/internal/brain.dm +++ b/code/modules/organs/internal/brain.dm @@ -83,8 +83,11 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) health = config.default_brain_health defib_timer = (config.defib_timer MINUTES) / 2 spawn(5) - if(brainmob && brainmob.client) - brainmob.client.screen.len = null //clear the hud + if(brainmob) + butcherable = FALSE + + if(brainmob.client) + brainmob.client.screen.len = null //clear the hud /obj/item/organ/internal/brain/Destroy() QDEL_NULL(brainmob) @@ -96,8 +99,9 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) brainmob = new(src) brainmob.name = H.real_name brainmob.real_name = H.real_name - brainmob.dna = H.dna.Clone() - brainmob.timeofhostdeath = H.timeofdeath + if(istype(H)) + brainmob.dna = H.dna.Clone() + brainmob.timeofhostdeath = H.timeofdeath // Copy modifiers. for(var/datum/modifier/M in H.modifiers) @@ -124,13 +128,13 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) if(name == initial(name)) name = "\the [owner.real_name]'s [initial(name)]" - var/mob/living/simple_mob/animal/borer/borer = owner.has_brain_worms() + var/mob/living/simple_mob/animal/borer/borer = owner?.has_brain_worms() if(borer) borer.detatch() //Should remove borer if the brain is removed - RR var/obj/item/organ/internal/brain/B = src - if(istype(B) && istype(owner)) + if(istype(B) && owner) B.transfer_identity(owner) ..() diff --git a/code/modules/organs/internal/liver.dm b/code/modules/organs/internal/liver.dm index 1fcfac93bb..c23467466a 100644 --- a/code/modules/organs/internal/liver.dm +++ b/code/modules/organs/internal/liver.dm @@ -8,7 +8,7 @@ /obj/item/organ/internal/liver/process() ..() - if(!owner) return + if(!iscarbon(owner)) return if(owner.life_tick % PROCESS_ACCURACY == 0) diff --git a/code/modules/organs/internal/robotic/robotic.dm b/code/modules/organs/internal/robotic/robotic.dm index 7a40ab8101..71414af1ae 100644 --- a/code/modules/organs/internal/robotic/robotic.dm +++ b/code/modules/organs/internal/robotic/robotic.dm @@ -9,3 +9,4 @@ decays = FALSE // Ditto. Rust takes a while. robotic = ORGAN_ROBOT + butcherable = FALSE diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm index 859a719fe9..d006648317 100644 --- a/code/modules/organs/organ.dm +++ b/code/modules/organs/organ.dm @@ -44,6 +44,9 @@ var/list/organ_cache = list() var/list/target_parent_classes = list() // Is the parent supposed to be organic, robotic, assisted? var/forgiving_class = TRUE // Will the organ give its verbs when it isn't a perfect match? I.E., assisted in organic, synthetic in organic. + var/butcherable = TRUE + var/meat_type // What does butchering, if possible, make? + /obj/item/organ/Destroy() handle_organ_mod_special(TRUE) @@ -59,21 +62,42 @@ var/list/organ_cache = list() /obj/item/organ/proc/update_health() return -/obj/item/organ/New(var/mob/living/carbon/holder, var/internal) +/obj/item/organ/New(var/mob/living/holder, var/internal) ..(holder) create_reagents(5) - if(!max_damage) - max_damage = min_broken_damage * 2 - if(istype(holder)) + + if(isliving(holder)) src.owner = holder src.w_class = max(src.w_class + mob_size_difference(holder.mob_size, MOB_MEDIUM), 1) //smaller mobs have smaller organs. + if(internal) + if(!LAZYLEN(holder.internal_organs)) + holder.internal_organs = list() + if(!LAZYLEN(holder.internal_organs_by_name)) + holder.internal_organs_by_name = list() + + holder.internal_organs |= src + holder.internal_organs_by_name[organ_tag] = src + + else + if(!LAZYLEN(holder.organs)) + holder.organs = list() + if(!LAZYLEN(holder.organs_by_name)) + holder.organs_by_name = list() + + holder.internal_organs |= src + holder.internal_organs_by_name[organ_tag] = src + + if(!max_damage) + max_damage = min_broken_damage * 2 + if(iscarbon(holder)) + var/mob/living/carbon/C = holder species = GLOB.all_species[SPECIES_HUMAN] if(holder.dna) - dna = holder.dna.Clone() + dna = C.dna.Clone() species = GLOB.all_species[dna.species] else log_debug("[src] at [loc] spawned without a proper DNA.") - var/mob/living/carbon/human/H = holder + var/mob/living/carbon/human/H = C if(istype(H)) if(internal) var/obj/item/organ/external/E = H.get_organ(parent_organ) @@ -81,18 +105,32 @@ var/list/organ_cache = list() if(E.internal_organs == null) E.internal_organs = list() E.internal_organs |= src - H.internal_organs_by_name[organ_tag] = src if(dna) if(!blood_DNA) blood_DNA = list() blood_DNA[dna.unique_enzymes] = dna.b_type - if(internal) - holder.internal_organs |= src else species = GLOB.all_species["Human"] handle_organ_mod_special() +/obj/item/organ/Initialize() + ..() + + if(owner) + if(!meat_type) + if(owner.isSynthetic()) + meat_type = /obj/item/stack/material/steel + else if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + meat_type = H?.species?.meat_type + + if(!meat_type) + if(owner.meat_type) + meat_type = owner.meat_type + else + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + /obj/item/organ/proc/set_dna(var/datum/dna/new_dna) if(new_dna) dna = new_dna.Clone() @@ -134,7 +172,7 @@ var/list/organ_cache = list() handle_organ_proc_special() //Process infections - if(robotic >= ORGAN_ROBOT || (owner && owner.species && (owner.species.flags & IS_PLANT || (owner.species.flags & NO_INFECT)))) + if(robotic >= ORGAN_ROBOT || (istype(owner) && (owner.species && (owner.species.flags & (IS_PLANT | NO_INFECT))))) germ_level = 0 return @@ -152,7 +190,7 @@ var/list/organ_cache = list() if(germ_level >= INFECTION_LEVEL_THREE) die() - else if(owner && owner.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs + else if(owner && owner?.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs //** Handle antibiotics and curing infections handle_antibiotics() handle_rejection() @@ -169,7 +207,7 @@ var/list/organ_cache = list() germ_level = 0 return 0 - var/antibiotics = owner.chem_effects[CE_ANTIBIOTIC] || 0 + var/antibiotics = iscarbon(owner) ? owner.chem_effects[CE_ANTIBIOTIC] || 0 : 0 var/infection_damage = 0 @@ -194,9 +232,9 @@ var/list/organ_cache = list() . = 1 //Organ qualifies for effect-specific processing //var/fever_temperature = (owner.species.heat_level_1 - owner.species.body_temperature - 5)* min(germ_level/INFECTION_LEVEL_TWO, 1) + owner.species.body_temperature //owner.bodytemperature += between(0, (fever_temperature - T20C)/BODYTEMP_COLD_DIVISOR + 1, fever_temperature - owner.bodytemperature) - var/fever_temperature = owner.species.heat_discomfort_level * 1.10 //Heat discomfort level plus 10% - if(owner.bodytemperature < fever_temperature) - owner.bodytemperature += min(0.2,(fever_temperature - owner.bodytemperature) / 10) //Will usually climb by 0.2, else 10% of the difference if less + var/fever_temperature = owner?.species.heat_discomfort_level * 1.10 //Heat discomfort level plus 10% + if(owner?.bodytemperature < fever_temperature) + owner?.bodytemperature += min(0.2,(fever_temperature - owner?.bodytemperature) / 10) //Will usually climb by 0.2, else 10% of the difference if less if (germ_level >= INFECTION_LEVEL_TWO) . = 2 //Organ qualifies for effect-specific processing @@ -211,7 +249,7 @@ var/list/organ_cache = list() // immunosuppressant that changes transplant data to make it match. if(dna && can_reject) if(!rejecting) - if(blood_incompatible(dna.b_type, owner.dna.b_type, species, owner.species)) + if(blood_incompatible(dna.b_type, owner?.dna.b_type, species, owner?.species)) rejecting = 1 else rejecting++ //Rejection severity increases over time. @@ -257,19 +295,20 @@ var/list/organ_cache = list() //Germs /obj/item/organ/proc/handle_antibiotics() - var/antibiotics = owner.chem_effects[CE_ANTIBIOTIC] || 0 + if(istype(owner)) + var/antibiotics = owner.chem_effects[CE_ANTIBIOTIC] || 0 - if (!germ_level || antibiotics < ANTIBIO_NORM) - return + if (!germ_level || antibiotics < ANTIBIO_NORM) + return - if (germ_level < INFECTION_LEVEL_ONE) - germ_level = 0 //cure instantly - else if (germ_level < INFECTION_LEVEL_TWO) - adjust_germ_level(-antibiotics*4) //at germ_level < 500, this should cure the infection in a minute - else if (germ_level < INFECTION_LEVEL_THREE) - adjust_germ_level(-antibiotics*2) //at germ_level < 1000, this will cure the infection in 5 minutes - else - adjust_germ_level(-antibiotics) // You waited this long to get treated, you don't really deserve this organ + if (germ_level < INFECTION_LEVEL_ONE) + germ_level = 0 //cure instantly + else if (germ_level < INFECTION_LEVEL_TWO) + adjust_germ_level(-antibiotics*4) //at germ_level < 500, this should cure the infection in a minute + else if (germ_level < INFECTION_LEVEL_THREE) + adjust_germ_level(-antibiotics*2) //at germ_level < 1000, this will cure the infection in 5 minutes + else + adjust_germ_level(-antibiotics) // You waited this long to get treated, you don't really deserve this organ //Adds autopsy data for used_weapon. /obj/item/organ/proc/add_autopsy_data(var/used_weapon, var/damage) @@ -292,7 +331,7 @@ var/list/organ_cache = list() //only show this if the organ is not robotic if(owner && parent_organ && amount > 0) - var/obj/item/organ/external/parent = owner.get_organ(parent_organ) + var/obj/item/organ/external/parent = owner?.get_organ(parent_organ) if(parent && !silent) owner.custom_pain("Something inside your [parent.name] hurts a lot.", amount) @@ -310,6 +349,7 @@ var/list/organ_cache = list() robotic = ORGAN_ASSISTED min_bruised_damage = 15 min_broken_damage = 35 + butcherable = FALSE /obj/item/organ/proc/digitize() //Used to make the circuit-brain. On this level in the event more circuit-organs are added/tweaks are wanted. robotize() @@ -329,31 +369,30 @@ var/list/organ_cache = list() take_damage(rand(1,3)) /obj/item/organ/proc/removed(var/mob/living/user) + if(owner) + owner.internal_organs_by_name[organ_tag] = null + owner.internal_organs_by_name -= organ_tag + owner.internal_organs_by_name -= null + owner.internal_organs -= src - if(!istype(owner)) - return + var/obj/item/organ/external/affected = owner.get_organ(parent_organ) + if(affected) affected.internal_organs -= src - owner.internal_organs_by_name[organ_tag] = null - owner.internal_organs_by_name -= organ_tag - owner.internal_organs_by_name -= null - owner.internal_organs -= src + forceMove(owner.drop_location()) + START_PROCESSING(SSobj, src) + rejecting = null - var/obj/item/organ/external/affected = owner.get_organ(parent_organ) - if(affected) affected.internal_organs -= src + if(istype(owner)) + var/datum/reagent/blood/organ_blood = locate(/datum/reagent/blood) in reagents.reagent_list + if(!organ_blood || !organ_blood.data["blood_DNA"]) + owner.vessel.trans_to(src, 5, 1, 1) - forceMove(owner.drop_location()) - START_PROCESSING(SSobj, src) - rejecting = null - var/datum/reagent/blood/organ_blood = locate(/datum/reagent/blood) in reagents.reagent_list - if(!organ_blood || !organ_blood.data["blood_DNA"]) - owner.vessel.trans_to(src, 5, 1, 1) - - if(owner && vital) - if(user) - add_attack_logs(user, owner, "Removed vital organ [src.name]") - if(owner.stat != DEAD) - owner.can_defib = 0 - owner.death() + if(owner && vital) + if(user) + add_attack_logs(user, owner, "Removed vital organ [src.name]") + if(owner.stat != DEAD) + owner.can_defib = 0 + owner.death() handle_organ_mod_special(TRUE) @@ -367,13 +406,13 @@ var/list/organ_cache = list() var/datum/reagent/blood/transplant_blood = locate(/datum/reagent/blood) in reagents.reagent_list transplant_data = list() if(!transplant_blood) - transplant_data["species"] = target.species.name - transplant_data["blood_type"] = target.dna.b_type - transplant_data["blood_DNA"] = target.dna.unique_enzymes + transplant_data["species"] = target?.species.name + transplant_data["blood_type"] = target?.dna.b_type + transplant_data["blood_DNA"] = target?.dna.unique_enzymes else - transplant_data["species"] = transplant_blood.data["species"] - transplant_data["blood_type"] = transplant_blood.data["blood_type"] - transplant_data["blood_DNA"] = transplant_blood.data["blood_DNA"] + transplant_data["species"] = transplant_blood?.data["species"] + transplant_data["blood_type"] = transplant_blood?.data["blood_type"] + transplant_data["blood_DNA"] = transplant_blood?.data["blood_DNA"] owner = target loc = owner @@ -416,6 +455,46 @@ var/list/organ_cache = list() bitten(user) return +/obj/item/organ/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(can_butcher(W, user)) + butcher(W, user) + return + + return ..() + +/obj/item/organ/proc/can_butcher(var/obj/item/O, var/mob/living/user) + if(butcherable && meat_type) + + if(istype(O, /obj/machinery/gibber)) // The great equalizer. + return TRUE + + if(robotic >= ORGAN_ROBOT) + if(O.is_screwdriver()) + return TRUE + + else + if(is_sharp(O) && has_edge(O)) + return TRUE + + return FALSE + +/obj/item/organ/proc/butcher(var/obj/item/O, var/mob/living/user, var/atom/newtarget) + if(robotic >= ORGAN_ROBOT) + user?.visible_message("[user] disassembles \the [src].") + + else + user?.visible_message("[user] butchers \the [src].") + + if(!newtarget) + newtarget = get_turf(src) + + var/obj/item/newmeat = new meat_type(newtarget) + + if(istype(newmeat, /obj/item/weapon/reagent_containers/food/snacks/meat)) + newmeat.name = "[src.name] [newmeat.name]" // "liver meat" "heart meat", etc. + + qdel(src) + /obj/item/organ/proc/organ_can_feel_pain() if(species.flags & NO_PAIN) return 0 diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm index 330b7a088c..51e82afc69 100644 --- a/code/modules/organs/organ_external.dm +++ b/code/modules/organs/organ_external.dm @@ -33,7 +33,8 @@ var/body_part = null // Part flag var/icon_position = 0 // Used in mob overlay layering calculations. var/model // Used when caching robolimb icons. - var/force_icon // Used to force override of species-specific limb icons (for prosthetics). + var/force_icon // Used to force override of species-specific limb icons (for prosthetics). Also used for any limbs chopped from a simple mob, and then attached to humans. + var/force_icon_key // Used to force the override of the icon-key generated using the species. Must be used in tandem with the above. var/icon/mob_icon // Cached icon for use in mob overlays. var/gendered_icon = 0 // Whether or not the icon state appends a gender. var/s_tone // Skin tone. @@ -96,7 +97,7 @@ qdel(splinted) splinted = null - if(owner) + if(istype(owner)) owner.organs -= src owner.organs_by_name[organ_tag] = null owner.organs_by_name -= organ_tag @@ -196,7 +197,7 @@ return dislocated = 1 - if(owner) + if(istype(owner)) owner.verbs |= /mob/living/carbon/human/proc/relocate /obj/item/organ/external/proc/relocate() @@ -204,7 +205,7 @@ return dislocated = 0 - if(owner) + if(istype(owner)) owner.shock_stage += 20 //check to see if we still need the verb @@ -220,7 +221,7 @@ /obj/item/organ/external/New(var/mob/living/carbon/holder) ..(holder, 0) - if(owner) + if(istype(owner)) replaced(owner) sync_colour_to_human(owner) spawn(1) @@ -867,11 +868,11 @@ Note that amputating the affected organ does in fact remove the infection from t var/mob/living/carbon/human/victim = owner //Keep a reference for post-removed(). var/obj/item/organ/external/parent_organ = parent - var/use_flesh_colour = species.get_flesh_colour(owner) - var/use_blood_colour = species.get_blood_colour(owner) + var/use_flesh_colour = species?.get_flesh_colour(owner) ? species.get_flesh_colour(owner) : "#C80000" + var/use_blood_colour = species?.get_blood_colour(owner) ? species.get_blood_colour(owner) : "#C80000" removed(null, ignore_children) - victim.traumatic_shock += 60 + victim?.traumatic_shock += 60 if(parent_organ) var/datum/wound/lost_limb/W = new (src, disintegrate, clean) @@ -887,9 +888,12 @@ Note that amputating the affected organ does in fact remove the infection from t stump.update_damages() spawn(1) - victim.updatehealth() - victim.UpdateDamageIcon() - victim.update_icons_body() + if(istype(victim)) + victim.updatehealth() + victim.UpdateDamageIcon() + victim.update_icons_body() + else + victim.update_icons() dir = 2 switch(disintegrate) @@ -1212,7 +1216,7 @@ Note that amputating the affected organ does in fact remove the infection from t organ.loc = src // Remove parent references - parent.children -= src + parent?.children -= src parent = null release_restraints(victim) diff --git a/code/modules/organs/organ_icon.dm b/code/modules/organs/organ_icon.dm index 084e94215b..c99df32c06 100644 --- a/code/modules/organs/organ_icon.dm +++ b/code/modules/organs/organ_icon.dm @@ -58,7 +58,7 @@ var/global/list/limb_icon_cache = list() cut_overlays() //Every 'addon' below requires information from species - if(!owner || !owner.species) + if(!iscarbon(owner) || !owner.species) return //Eye color/icon @@ -135,7 +135,10 @@ var/global/list/limb_icon_cache = list() if(owner && owner.gender == FEMALE) gender = "f" - icon_cache_key = "[icon_name]_[species ? species.name : SPECIES_HUMAN]" + if(!force_icon_key) + icon_cache_key = "[icon_name]_[species ? species.name : SPECIES_HUMAN]" + else + icon_cache_key = "[icon_name]_[force_icon_key]" if(force_icon) mob_icon = new /icon(force_icon, "[icon_name][gendered_icon ? "_[gender]" : ""]") diff --git a/code/modules/organs/subtypes/machine.dm b/code/modules/organs/subtypes/machine.dm index 48641f5d71..4579f6f798 100644 --- a/code/modules/organs/subtypes/machine.dm +++ b/code/modules/organs/subtypes/machine.dm @@ -38,6 +38,7 @@ var/brain_type = /obj/item/device/mmi var/obj/item/device/mmi/stored_mmi robotic = ORGAN_ASSISTED + butcherable = FALSE /obj/item/organ/internal/mmi_holder/Destroy() if(stored_mmi && (stored_mmi.loc == src)) diff --git a/code/modules/organs/subtypes/standard.dm b/code/modules/organs/subtypes/standard.dm index d14a0b8d38..91b75304b7 100644 --- a/code/modules/organs/subtypes/standard.dm +++ b/code/modules/organs/subtypes/standard.dm @@ -282,14 +282,15 @@ /obj/item/organ/external/head/removed() if(owner) - name = "[owner.real_name]'s head" - owner.drop_from_inventory(owner.glasses) - owner.drop_from_inventory(owner.head) - owner.drop_from_inventory(owner.l_ear) - owner.drop_from_inventory(owner.r_ear) - owner.drop_from_inventory(owner.wear_mask) - spawn(1) - owner.update_hair() + if(iscarbon(owner)) + name = "[owner.real_name]'s head" + owner.drop_from_inventory(owner.glasses) + owner.drop_from_inventory(owner.head) + owner.drop_from_inventory(owner.l_ear) + owner.drop_from_inventory(owner.r_ear) + owner.drop_from_inventory(owner.wear_mask) + spawn(1) + owner.update_hair() get_icon() ..() diff --git a/html/changelogs/mechoid - butchery.yml b/html/changelogs/mechoid - butchery.yml new file mode 100644 index 0000000000..433bf787e7 --- /dev/null +++ b/html/changelogs/mechoid - butchery.yml @@ -0,0 +1,37 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Mechoid + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Animals can be butchered for organs and hide. Requires scraping (sharp), washing (water or washing machine), and then drying (bonfire or drying rack)." + - rscadd: "Organs can be butchered for meat, named "[organ] meat". Heart meat, liver meat, etc. Brains from player mobs cannot be butchered." diff --git a/icons/obj/butchery.dmi b/icons/obj/butchery.dmi new file mode 100644 index 0000000000000000000000000000000000000000..eef08d1aa76df780390fe373a3c5b8c078c189fb GIT binary patch literal 372 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 diff --git a/polaris.dme b/polaris.dme index 62b3ac8aef..6fa203e50a 100644 --- a/polaris.dme +++ b/polaris.dme @@ -2123,6 +2123,7 @@ #include "code\modules\mob\language\station.dm" #include "code\modules\mob\language\synthetic.dm" #include "code\modules\mob\living\autohiss.dm" +#include "code\modules\mob\living\butchering.dm" #include "code\modules\mob\living\damage_procs.dm" #include "code\modules\mob\living\death.dm" #include "code\modules\mob\living\default_language.dm" @@ -2135,6 +2136,7 @@ #include "code\modules\mob\living\living_powers.dm" #include "code\modules\mob\living\login.dm" #include "code\modules\mob\living\logout.dm" +#include "code\modules\mob\living\organs.dm" #include "code\modules\mob\living\say.dm" #include "code\modules\mob\living\status_indicators.dm" #include "code\modules\mob\living\bot\bot.dm" @@ -2302,6 +2304,7 @@ #include "code\modules\mob\living\silicon\robot\subtypes\lost_drone.dm" #include "code\modules\mob\living\silicon\robot\subtypes\syndicate.dm" #include "code\modules\mob\living\simple_mob\appearance.dm" +#include "code\modules\mob\living\simple_mob\butchering.dm" #include "code\modules\mob\living\simple_mob\combat.dm" #include "code\modules\mob\living\simple_mob\defense.dm" #include "code\modules\mob\living\simple_mob\hands.dm"