Skip to content
1 change: 1 addition & 0 deletions cev_eris.dme
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@
#include "code\datums\autolathe\tools.dm"
#include "code\datums\changelog\changelog.dm"
#include "code\datums\components\_component.dm"
#include "code\datums\components\butchering.dm"
#include "code\datums\components\clothing_sanity_protection.dm"
#include "code\datums\components\fabric.dm"
#include "code\datums\components\jamming.dm"
Expand Down
2 changes: 2 additions & 0 deletions code/__DEFINES/is_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur")))

#define isgolem(A) istype(A, /mob/living/carbon/superior_animal/golem)

#define isspider(A) istype(A, /mob/living/carbon/superior_animal/giant_spider)

#define isbrain(A) istype(A, /mob/living/carbon/brain)

#define ishuman(A) istype(A, /mob/living/carbon/human)
Expand Down
24 changes: 24 additions & 0 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,27 @@ GLOBAL_REAL_VAR(list/voice_type2sound = list(

///Managed global that is a reference to the real global
GLOBAL_LIST_INIT(voice_type2sound_ref, voice_type2sound)

//butchering defines
//defines for base success chance
#define BUTCHER_IMPOSSIBLE 5
#define BUTCHER_VERY_HARD 10
#define BUTCHER_HARD 25
#define BUTCHER_CHALLENGING 35
#define BUTCHER_DIFFICULT 45
#define BUTCHER_NORMAL 55
#define BUTCHER_EASY 65
#define BUTCHER_VERY_EASY 80
#define BUTCHER_EFFORTLESS 100//still technically possible to fail with terrible tool + 0 bio, so not 'zero'

/**
* divisor for BIO effect on butchering
* values of bio above this will improve butchering chances, values below will dmg it
*/
#define BUTCHER_BIO_DIVISOR 25

/**
* the base chance of a butchery going wrong and triggering a hazard effect
* increased by 10 each time you fail to extract an item
*/
#define BUTCHERING_HAZARD_CHANCE 5
141 changes: 141 additions & 0 deletions code/datums/components/butchering.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/datum/component/butchering
/// Whether or not this component can be used to butcher currently. Used to temporarily disable an existing butcher component for later
var/butchering_enabled = TRUE

/// Whether or not this component is compatible with blunt tools.
var/can_be_blunt = FALSE

// Chance of triggering an additional hazard effect when butchering an animal.
var/hazard_chance = BUTCHERING_HAZARD_CHANCE

/datum/component/butchering/Initialize(
disabled = FALSE,
can_be_blunt = FALSE,
)
if(disabled)
src.butchering_enabled = FALSE
src.can_be_blunt = can_be_blunt
if(isitem(parent))
RegisterSignal(parent, COMSIG_IATTACK, PROC_REF(onItemAttack))
RegisterSignal(parent, COMSIG_APPVAL, PROC_REF(onStatusChange))

///checks if we can butcher, and intercepts the attack chain if we successfully do so
/datum/component/butchering/proc/onItemAttack(atom/target, mob/living/user, params)
SIGNAL_HANDLER
var/obj/item/ourparent
if(isitem(parent))
ourparent = parent

if(!(user.a_intent == I_HURT))//are we on harm intent?
return

if(isliving(target))
var/mob/living/ourmob = target
if(ourmob.stat == DEAD && (ourmob.butcher_results)) //can we butcher it?
if(butchering_enabled && (can_be_blunt || ourparent.sharp))
INVOKE_ASYNC(src, PROC_REF(startButcher), ourparent, ourmob, user)
return TRUE //ends attack chain, so all we do is butcher
return

///handles the use tool containing our butchering action. needed since signal_handler procs hate do afters
/datum/component/butchering/proc/startButcher(obj/item/source, mob/living/meat, mob/living/user)
to_chat(user, span_notice("You begin to butcher \the [meat]..."))
var/needed_quality = QUALITY_CUTTING
if(!source.has_quality(QUALITY_CUTTING))
needed_quality = null
if(source.use_tool(user, meat, WORKTIME_NORMAL, needed_quality, FAILCHANCE_NORMAL, required_stat = STAT_BIO))
on_butchering(user, meat, source)
/**
* Handles a user butchering a target
*
* Arguments:
* - [butcher][/mob/living]: The mob doing the butchering
* - [meat][/mob/living]: The mob being butchered
* - [source][/obj/item]: The item holding our component, expressly typed
*/
/datum/component/butchering/proc/on_butchering(mob/living/butcher, mob/living/meat, obj/item/source)
//our final items to spawn
var/list/butchered = list()
//did we fail to extract something?
var/mulched
var/turf/dropturf = get_turf(meat)
var/bio = butcher.stats.getStat(STAT_BIO)
//mult to success chance based on tool quality
var/toolpowr = 0.5

if(source.has_quality(QUALITY_CUTTING))//otherwise, we're working with something stupid like a glass shard or a beartrap(truly, advanced cutting tool)
toolpowr = round((source.get_tool_quality(QUALITY_CUTTING) / 10))

if(!meat.butcher_results)
log_runtime(" [meat.type] was butchered without any possible butcher results.")
meat.gib()

//for getting the dialogue hinting at relative power
var/msgpowr = toolpowr * (max((bio / BUTCHER_BIO_DIVISOR), 0.25))
switch(msgpowr)
if(0 to 0.8)//go get an actual knife you gross-ass roundstart vagabond
butcher.visible_message(span_bolddanger("[butcher] can't get anywhere with this tool! Instead, they rip \the [meat] to shreds like an animal!"))
if(0.9 to 3.5)
butcher.visible_message(span_danger("[butcher] messily chops up \the [meat]!"))
if(3.6 to 6)
butcher.visible_message(span_danger("[butcher] carefully butchers \the [meat]."))
if(6.1 to 99)
butcher.visible_message(span_notice("[butcher] precisely dissects \the [meat]."))

for(var/thing in meat.butcher_results)
//get the assoc list to get our stored info
var/list/resultlist = meat.butcher_results[thing]
//pull out the number of drops
var/number = resultlist[1]
//pull out the base percentage of awarding this result
var/difficulty = resultlist[2]
//takes difficulty and multiplies it by tool and bio stat to get final chance
var/trueprob = clamp((difficulty * toolpowr) * max((bio / BUTCHER_BIO_DIVISOR), 0.25), 1, 100)
for(var/result in 1 to number)
if(prob(trueprob))
butchered += thing
else
mulched++
hazard_chance = min(hazard_chance + 10, 100)//chance of complications increases for each item you fail to harvest

if(mulched)
to_chat(butcher, span_warning("You [LAZYLEN(meat.butcher_results) <= mulched ? "completely destroyed all of" : "lost some of"] the harvest from \the [meat]."))
if(LAZYLEN(butchered))
for(var/reward in butchered)
var/obj/item/ourdrop = new reward(dropturf)
ourdrop.name = "[meat.name] [ourdrop.name]"
if(istype(ourdrop, /obj/item/reagent_containers/food/snacks))
var/obj/item/reagent_containers/food/snacks/ourmeat = ourdrop
ourmeat.food_quality = (ourmeat.food_quality * ((bio + 15) / 15) * clamp((toolpowr / 15), 0.25, 4))


//try to invoke a hazard effect on the butcher
if(meat.butchery_hazard && prob(hazard_chance))
butcher.visible_message(span_danger("While cutting up \the [meat], [butcher]'s hand slips..."), span_danger("While cutting up \the [meat], your hand slips..."))
meat.butchery_fail(butcher)

//let's finish up.
meat.drop_embedded()
meat.gib()
if(meat.client)//if a player just got hardgibbed, tattle
message_admins("[meat] ([key_name(meat)]) was butchered for meat by [butcher] ([key_name(butcher)]) [ADMIN_JMP(butcher)].")

///Enables the butchering mechanic.
/datum/component/butchering/proc/enable_butchering(datum/source)
SIGNAL_HANDLER
butchering_enabled = TRUE

///Disables the butchering mechanic.
/datum/component/butchering/proc/disable_butchering(datum/source)
SIGNAL_HANDLER
butchering_enabled = FALSE

///signal check to see if our holder is still sharp, for variable butchering items. If not, shut down the component till sharpness comes back.
/datum/component/butchering/proc/onStatusChange(atom/holder)
SIGNAL_HANDLER
if(isitem(holder))
var/obj/item/itemholder = holder
if(!itemholder.sharp && !can_be_blunt)
disable_butchering()
else if(itemholder.sharp && butchering_enabled == FALSE)
enable_butchering()
4 changes: 3 additions & 1 deletion code/game/jobs/job/civilian.dm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
wage = WAGE_NONE // Makes his own money
department_account_access = TRUE
stat_modifiers = list(
STAT_BIO = 20,
STAT_ROB = 15,
STAT_TGH = 15,
STAT_VIG = 15,
Expand Down Expand Up @@ -59,8 +60,9 @@
wage = WAGE_NONE //They should get paid by the club owner, otherwise you know what to do.
department_account_access = TRUE
stat_modifiers = list(
STAT_BIO = 25,
STAT_ROB = 10,
STAT_TGH = 10,
STAT_TGH = 5,
STAT_VIG = 5,
)
outfit_type = /decl/hierarchy/outfit/job/service/waiter
Expand Down
12 changes: 6 additions & 6 deletions code/game/machinery/kitchen/gibber.dm
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,17 @@

var/meat_amount = occupant.mob_size / 2
var/meat_type = /obj/item/reagent_containers/food/snacks/meat
if(issuperioranimal(occupant))
var/mob/living/carbon/superior_animal/S = occupant
meat_type = S.meat_type
if(issuperioranimal(occupant) || isanimal(occupant))
if(occupant.butcher_results)//nab a meat from the occupant's butcher results. Otherwise use default meat
for(var/possiblemeat in occupant.butcher_results)
if(istype(possiblemeat, /obj/item/reagent_containers/food/snacks/meat))
meat_type = possiblemeat
break
else if(iscarbon(occupant))
var/mob/living/carbon/C = occupant
meat_type = C.species.meat_type
if(occupant.stats.getPerk(PERK_SURVIVOR))
meat_type = /obj/item/reagent_containers/food/snacks/meat/pork
else if(isanimal(occupant))
var/mob/living/simple_animal/A = occupant
meat_type = A.meat_type

for(var/i in 1 to meat_amount)
var/obj/item/reagent_containers/food/snacks/meat/new_meat = new meat_type(src)
Expand Down
2 changes: 2 additions & 0 deletions code/game/objects/items.dm
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@
if(chameleon_type)
verbs.Add(/obj/item/proc/set_chameleon_appearance)
tact_visual = new /obj/effect/effect/melee/alert
if(sharp && !isProjectile(src))//any item with sharpness on spawn gets butchering
AddComponent(/datum/component/butchering, FALSE, FALSE)
. = ..()

/obj/item/Destroy(force)
Expand Down
2 changes: 2 additions & 0 deletions code/game/objects/items/weapons/tools/mods/_upgrades.dm
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@
T.max_upgrades += tool_upgrades[UPGRADE_MAXUPGRADES]
if(tool_upgrades[UPGRADE_SHARP])
T.sharp = tool_upgrades[UPGRADE_SHARP]
if(!T.GetComponent(/datum/component/butchering))
T.AddComponent(/datum/component/butchering, FALSE, FALSE)
if(tool_upgrades[UPGRADE_COLOR])
T.color = tool_upgrades[UPGRADE_COLOR]
if(tool_upgrades[UPGRADE_ITEMFLAGPLUS])
Expand Down
3 changes: 1 addition & 2 deletions code/modules/holodeck/HolodeckObjects.dm
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,7 @@
icon_dead = "holo4"
alpha = 127
icon_gib = null
meat_amount = 0
meat_type = null
butcher_results = null

/mob/living/simple_animal/hostile/carp/holodeck/New()
..()
Expand Down
19 changes: 19 additions & 0 deletions code/modules/mob/living/carbon/carbon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,22 @@
to_chat(usr, "Removed [rem_organ] from [src].")
rem_organ.removed()
qdel(rem_organ)

/// checks if a location on a mob is protected from skin-contact effects. Returns null if no protection, or the protecting item if protected
/mob/living/carbon/proc/find_skin_protection(var/protection_zone)
var/obj/item/protecting_item
var/list/protection_items
if(!ishuman(src))//if human, check human inventory slots
var/mob/living/carbon/human/ashuman = src
protection_items = list(ashuman.head, ashuman.glasses, ashuman.wear_mask, ashuman.wear_suit, ashuman.w_uniform, ashuman.gloves, ashuman.shoes)
else //otherwise just check for a mask
protection_items = list(src.wear_mask)

for(var/obj/item/ourcloth in protection_items)
if((ourcloth && ourcloth.body_parts_covered & protection_zone))
if(!(ourcloth.item_flags & FLEXIBLEMATERIAL) && !(ourcloth.item_flags & AIRTIGHT))//flexible non-airtight items are porous and let liquids/gas through
protecting_item = ourcloth.name
break
return protecting_item


25 changes: 2 additions & 23 deletions code/modules/mob/living/carbon/superior_animal/defense.dm
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
/mob/living/carbon/superior_animal/proc/harvest(mob/user)
var/actual_meat_amount = max(1,(meat_amount/2))
if(meat_type && actual_meat_amount>0 && (stat == DEAD))
drop_embedded()
for(var/i=0;i<actual_meat_amount;i++)
var/obj/item/meat = new meat_type(get_turf(src))
meat.name = "[src.name] [meat.name]"
if(issmall(src))
user.visible_message(span_danger("[user] chops up \the [src]!"))
var/obj/effect/decal/cleanable/blood/blood_effect = new/obj/effect/decal/cleanable/blood/splatter(get_turf(src))
blood_effect.basecolor = bloodcolor
blood_effect.update_icon()
qdel(src)
else
user.visible_message(span_danger("[user] butchers \the [src] messily!"))
gib()

/mob/living/carbon/superior_animal/update_lying_buckled_and_verb_status()
..()

Expand All @@ -25,12 +8,8 @@
updatehealth()

/mob/living/carbon/superior_animal/attackby(obj/item/I, mob/living/user, params)
if (meat_type && (stat == DEAD) && (QUALITY_CUTTING in I.tool_qualities))
if (I.use_tool(user, src, WORKTIME_NORMAL, QUALITY_CUTTING, FAILCHANCE_NORMAL, required_stat = STAT_BIO))
harvest(user)
else
. = ..()
updatehealth()
. = ..()
updatehealth()

/mob/living/carbon/superior_animal/resolve_item_attack(obj/item/I, mob/living/user, hit_zone)
//mob.attackby -> item.attack -> mob.resolve_item_attack -> item.apply_hit_effect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
move_to_delay = 5
turns_per_move = 5
see_in_dark = 10
meat_type = /obj/item/reagent_containers/food/snacks/meat/spider
meat_amount = 3
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/spider = list(3, BUTCHER_NORMAL))
stop_automated_movement_when_pulled = 0

melee_damage_lower = 12
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@
melee_damage_upper = 20
poison_per_bite = 8
move_to_delay = 3
meat_type = /obj/item/reagent_containers/food/snacks/meat/spider/hunter
meat_amount = 4
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/spider/hunter = list(3, BUTCHER_NORMAL))
rarity_value = 75
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
poison_per_bite = 3
var/atom/cocoon_target
poison_type = "aranecolmin"
meat_type = /obj/item/reagent_containers/food/snacks/meat/spider/nurse
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/spider/nurse = list(3, BUTCHER_NORMAL))
move_to_delay = 4
meat_amount = 3
rarity_value = 75
var/fed = 0
var/egg_inject_chance = 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@
speak_chance = 5

see_in_dark = 10
meat_type = null
meat_amount = 0
butcher_results = null
stop_automated_movement_when_pulled = 0
wander = FALSE
viewRange = 8
Expand Down
4 changes: 2 additions & 2 deletions code/modules/mob/living/carbon/superior_animal/roach/life.dm
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@
// End message
src.visible_message(span_warning("\The [src] finishes eating \the [eat_target], leaving only bones."))
// Get fed
fed += rand(1,tasty.meat_amount)
fed += rand(1,LAZYLEN(tasty.butcher_results))
if (isroach(tasty))
var/mob/living/carbon/superior_animal/roach/cannibalism = tasty
fed += cannibalism.fed
if(istype(src, /mob/living/carbon/superior_animal/roach/roachling))
if(tasty.meat_amount >= 6)// ate a fuhrer or kaiser
if(istype(tasty, /mob/living/carbon/superior_animal/roach/kaiser) || istype(tasty, /mob/living/carbon/superior_animal/roach/fuhrer))// ate a fuhrer or kaiser
var/mob/living/carbon/superior_animal/roach/roachling/bigboss = src
bigboss.big_boss = TRUE
clearEatTarget()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
turns_per_move = 4
turns_since_move = 0

meat_type = /obj/item/reagent_containers/food/snacks/meat/roachmeat/kampfer
meat_amount = 2
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/roachmeat/kampfer = list(3, BUTCHER_NORMAL))

maxHealth = 10
health = 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
maxHealth = 25
health = 25
melee_damage_upper = 3
meat_type = /obj/item/reagent_containers/food/snacks/meat/roachmeat/benzin
meat_amount = 3
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/roachmeat/benzin = list(3, BUTCHER_DIFFICULT))
rarity_value = 15


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
icon_state = "bluespaceroach"
maxHealth = 25
health = 25
meat_type = /obj/item/bluespace_crystal
butcher_results = list(/obj/item/bluespace_crystal = list(4, BUTCHER_CHALLENGING))
melee_damage_lower = 4
melee_damage_upper = 11
armor_divisor = ARMOR_PEN_MAX // Hits through armor
Expand Down
Loading
Loading