Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
2 changes: 2 additions & 0 deletions 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 = 25,
STAT_ROB = 15,
STAT_TGH = 15,
STAT_VIG = 15,
Expand Down Expand Up @@ -59,6 +60,7 @@
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 = 18,
STAT_ROB = 10,
STAT_TGH = 10,
STAT_VIG = 5,
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