code:
// This is just a bit of fun while making an example for global signal
/datum/component/edit_complainer
	var/list/say_lines

/datum/component/edit_complainer/Initialize(list/text)
	if(!ismovable(parent))
		return COMPONENT_INCOMPATIBLE

	var/static/list/default_lines = list(
		"CentCom's profligacy frays another thread.",
		"Another tug at the weave.",
		"Who knows when the stresses will finally shatter the form?",
		"Even now a light shines through the cracks.",
		"CentCom once more twists knowledge beyond its authority.",
		"There is an uncertain air in the mansus.",
		)
	say_lines = text || default_lines

	RegisterSignal(SSdcs, COMSIG_GLOB_VAR_EDIT, .proc/var_edit_react)

/datum/component/edit_complainer/proc/var_edit_react(datum/source, list/arguments)
	SIGNAL_HANDLER

	var/atom/movable/master = parent
	master.say(pick(say_lines))


code:
/**
 * # egg layer component!
 *
 * Component that manages how many eggs to lay, what can be fed to the mob to make them lay more, and what is actually laid.
 * Since the only real interaction with the component is an attackby, the nice part is that we're able to make this an atom level proc.
 * egg_layer will loudly fail if you do not provide it the arguments, as to encourage explicicy(?)
 */
/datum/component/egg_layer
	/// item laid by the mob
	var/egg_type
	/// items that can be fed to the mob to make it lay more eggs
	var/list/food_types
	/// messages sent when fed
	var/list/feed_messages
	/// messages sent when laying an egg
	var/list/lay_messages
	/// how many eggs left to lay
	var/eggs_left
	/// how many eggs to lay given from food
	var/eggs_added_from_eating
	/// how many eggs can be stored
	var/max_eggs_held
	/// callback to a proc that allows the parent to modify their new eggs
	var/datum/callback/egg_laid_callback

/datum/component/egg_layer/Initialize(egg_type, food_types, feed_messages, lay_messages, eggs_left, eggs_added_from_eating, max_eggs_held, egg_laid_callback)
	if(!isatom(parent)) //yes, you could make a tameable toolbox.
		return COMPONENT_INCOMPATIBLE

	src.egg_type = egg_type
	src.food_types = food_types
	src.feed_messages = feed_messages
	src.lay_messages = lay_messages
	src.eggs_left = eggs_left
	src.eggs_added_from_eating = eggs_added_from_eating
	src.max_eggs_held = max_eggs_held
	src.egg_laid_callback = egg_laid_callback

	START_PROCESSING(SSobj, src)

/datum/component/egg_layer/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/feed_food)

/datum/component/egg_layer/UnregisterFromParent()
	. = ..()
	UnregisterSignal(parent, COMSIG_PARENT_ATTACKBY)

/datum/component/egg_layer/Destroy(force, silent)
	. = ..()
	STOP_PROCESSING(SSobj, src)

/datum/component/egg_layer/proc/feed_food(datum/source, obj/item/food, mob/living/attacker, params)
	SIGNAL_HANDLER

	var/atom/at_least_atom = parent
	if(!is_type_in_list(food, food_types))
		return
	if(isliving(at_least_atom))
		var/mob/living/potentially_dead_horse = at_least_atom
		if(potentially_dead_horse.stat == DEAD)
			to_chat(attacker, span_warning("[parent] is dead!"))
			return COMPONENT_CANCEL_ATTACK_CHAIN
	if(eggs_left > max_eggs_held)
		to_chat(attacker, span_warning("[parent] doesn't seem hungry!"))
		return COMPONENT_CANCEL_ATTACK_CHAIN
	attacker.visible_message(span_notice("[attacker] hand-feeds [food] to [parent]."), span_notice("You hand-feed [food] to [parent]."))
	at_least_atom.visible_message(pick(feed_messages))
	qdel(food)
	eggs_left += min(eggs_left + eggs_added_from_eating, max_eggs_held)
	return COMPONENT_CANCEL_ATTACK_CHAIN

/datum/component/egg_layer/process(delta_time = SSOBJ_DT)

	var/atom/at_least_atom = parent
	if(isliving(at_least_atom))
		var/mob/living/potentially_dead_horse = at_least_atom
		if(potentially_dead_horse.stat != CONSCIOUS)
			return
	if(!eggs_left || !DT_PROB(1.5, delta_time))
		return

	at_least_atom.visible_message(span_alertalien("[at_least_atom] [pick(lay_messages)]"))
	eggs_left--
	var/obj/item/egg = new egg_type(get_turf(at_least_atom))
	egg.pixel_x = rand(-6, 6)
	egg.pixel_y = rand(-6, 6)
	egg_laid_callback?.Invoke(egg)

repo_name: AnalWerewolf/sundown

license: mit


code:
///divide the power in the cable net under parent by this to determine the shock damage
#define ELECTRIC_BUCKLE_SHOCK_STRENGTH_DIVISOR 5000
///it will not shock the mob buckled to parent if its required to use a cable to shock and the cable has less than this power availaible
#define ELECTRIC_BUCKLE_MINUMUM_POWERNET_STRENGTH 10

/**
 * # electrified_buckle component:
 * attach it to any atom/movable that can be buckled to in order to have it shock mobs buckled to it. by default it shocks mobs buckled to parent every shock_loop_time.
 * the parent is supposed to define its behavior with arguments in AddComponent
*/
/datum/component/electrified_buckle
	///if usage_flags has SHOCK_REQUIREMENT_ITEM, this is the item required to be inside parent in order for it to shock buckled mobs
	var/obj/item/required_object
	///this is casted to the overlay we put on parent_chair
	var/list/requested_overlays
	///it will only shock once every shock_loop_time
	COOLDOWN_DECLARE(electric_buckle_cooldown)
	///these flags tells this instance what is required in order to allow shocking
	var/usage_flags
	///if true, this will shock the buckled mob every shock_loop_time in process()
	var/shock_on_loop = TRUE
	///how long the component waits before shocking the mob buckled to parent again
	var/shock_loop_time = 5 SECONDS
	///how much damage is done per shock iff usage_flags doesnt have SHOCK_REQUIREMENT_LIVE_CABLE
	var/shock_damage = 50
	///this signal was given as an argument to register for parent to emit, if its emitted to parent then shock_on_demand is called. var is so it can be unregistered
	var/requested_signal_parent_emits = null

/**
 * Initialize args:
 *
 * * input_requirements - bitflag that defines how the component is supposed to act, see __DEFINES/electrified_buckle.dm for the options. sets usage_flags
 * * input_item - if set to an item and input_requirements has SHOCK_REQUIREMENT_ITEM, moves that item inside parent and the component will delete itself if input_item no longer exists/moves out of parent. sets required_object
 * * overlays_to_add - pass in a list of images and the component will add them to parent as well as remove them in UnregisterFromParent(). sets requested_overlays
 * * override_buckle - if TRUE, sets parent.can_buckle = TRUE and resets it on UnregisterFromParent(), usually objects that have need to be overridden will look janky on buckle
 * * damage_on_shock - if SHOCK_REQUIREMENT_LIVE_CABLE is not set in input_requirements, then this is how much damage each shock does. sets shock_damage
 * * signal_to_register_from_parent - if set, the component registers to listen for this signal targeting parent to manually shock. sets requested_signal_parent_emits
*/
/datum/component/electrified_buckle/Initialize(input_requirements, obj/item/input_item, list/overlays_to_add, override_buckle, damage_on_shock = 50, signal_to_register_from_parent, loop_length)
	var/atom/movable/parent_as_movable = parent
	if(!istype(parent_as_movable))
		return COMPONENT_INCOMPATIBLE

	usage_flags = input_requirements

	if(!parent_as_movable.can_buckle && !override_buckle)
		return COMPONENT_INCOMPATIBLE
	else if (override_buckle)
		parent_as_movable.can_buckle = TRUE

	if((usage_flags & SHOCK_REQUIREMENT_ITEM) && QDELETED(input_item))
		return COMPONENT_INCOMPATIBLE

	if(HAS_TRAIT(parent_as_movable, TRAIT_ELECTRIFIED_BUCKLE))
		return COMPONENT_INCOMPATIBLE

	if(usage_flags & SHOCK_REQUIREMENT_ITEM)
		required_object = input_item
		required_object.Move(parent_as_movable)
		RegisterSignal(required_object, COMSIG_PARENT_PREQDELETED, .proc/delete_self)
		RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), .proc/move_required_object_from_contents)

		if(usage_flags & SHOCK_REQUIREMENT_ON_SIGNAL_RECEIVED)
			shock_on_loop = FALSE
			RegisterSignal(required_object, COMSIG_ASSEMBLY_PULSED, .proc/shock_on_demand)
		else if(usage_flags & SHOCK_REQUIREMENT_SIGNAL_RECEIVED_TOGGLE)
			RegisterSignal(required_object, COMSIG_ASSEMBLY_PULSED, .proc/toggle_shock_loop)

	if((usage_flags & SHOCK_REQUIREMENT_PARENT_MOB_ISALIVE) && ismob(parent))
		RegisterSignal(parent, COMSIG_LIVING_DEATH, .proc/delete_self)

	RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/on_buckle)

	ADD_TRAIT(parent_as_movable, TRAIT_ELECTRIFIED_BUCKLE, INNATE_TRAIT)

	//if parent wants us to manually shock on some specified action
	if(signal_to_register_from_parent)
		RegisterSignal(parent, signal_to_register_from_parent, .proc/shock_on_demand)
		requested_signal_parent_emits = signal_to_register_from_parent

	if(overlays_to_add)
		requested_overlays = overlays_to_add
		parent_as_movable.add_overlay(requested_overlays)

	parent_as_movable.name = "electrified [initial(parent_as_movable.name)]"

	shock_damage = damage_on_shock

	if(loop_length)
		shock_loop_time = loop_length

	if(parent_as_movable.has_buckled_mobs())
		for(var/mob/living/possible_guinea_pig as anything in parent_as_movable.buckled_mobs)
			if(on_buckle(src, possible_guinea_pig))
				break

/datum/component/electrified_buckle/UnregisterFromParent()
	var/atom/movable/parent_as_movable = parent

	parent_as_movable.cut_overlay(requested_overlays)
	parent_as_movable.name = initial(parent_as_movable.name)
	parent_as_movable.can_buckle = initial(parent_as_movable.can_buckle)

	if(parent)
		REMOVE_TRAIT(parent_as_movable, TRAIT_ELECTRIFIED_BUCKLE, INNATE_TRAIT)
		UnregisterSignal(parent, list(COMSIG_MOVABLE_BUCKLE, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER)))
		if(requested_signal_parent_emits)
			UnregisterSignal(parent, requested_signal_parent_emits)

	if(required_object)
		UnregisterSignal(required_object, list(COMSIG_PARENT_PREQDELETED, COMSIG_ASSEMBLY_PULSED))
		if(parent_as_movable && (required_object in parent_as_movable.contents))
			required_object.Move(parent_as_movable.loc)

	required_object = null
	STOP_PROCESSING(SSprocessing, src)

/datum/component/electrified_buckle/proc/delete_self()
	SIGNAL_HANDLER
	qdel(src)

/datum/component/electrified_buckle/proc/move_required_object_from_contents(datum/source, mob/living/user, obj/item/tool, tool_type)
	SIGNAL_HANDLER
	var/atom/movable/parent_as_movable = parent
	if(!QDELETED(parent_as_movable))
		tool.play_tool_sound(parent_as_movable)
		required_object.Move(parent_as_movable.loc)
	qdel(src)

/datum/component/electrified_buckle/proc/on_buckle(datum/source, mob/living/mob_to_buckle, _force)
	SIGNAL_HANDLER
	if(!istype(mob_to_buckle))
		return FALSE

	COOLDOWN_START(src, electric_buckle_cooldown, shock_loop_time)
	if(!(usage_flags & SHOCK_REQUIREMENT_ON_SIGNAL_RECEIVED) && shock_on_loop)
		START_PROCESSING(SSprocessing, src)
	return TRUE

///where the guinea pig is actually shocked if possible
/datum/component/electrified_buckle/process(delta_time)
	var/atom/movable/parent_as_movable = parent
	if(QDELETED(parent_as_movable) || !parent_as_movable.has_buckled_mobs())
		return PROCESS_KILL

	if(!shock_on_loop)
		return PROCESS_KILL

	if(!COOLDOWN_FINISHED(src, electric_buckle_cooldown))
		return

	COOLDOWN_START(src, electric_buckle_cooldown, shock_loop_time)

	var/turf/our_turf = get_turf(parent_as_movable)
	var/obj/structure/cable/live_cable = our_turf.get_cable_node()

	if(usage_flags & SHOCK_REQUIREMENT_LIVE_CABLE)
		if((!live_cable || !live_cable.powernet || live_cable.powernet.avail < ELECTRIC_BUCKLE_MINUMUM_POWERNET_STRENGTH))
			return

		for(var/mob/living/guinea_pig as anything in parent_as_movable.buckled_mobs)
			guinea_pig.electrocute_act(round(live_cable.powernet.avail / ELECTRIC_BUCKLE_SHOCK_STRENGTH_DIVISOR), parent_as_movable)
			break
	else
		for(var/mob/living/guinea_pig as anything in parent_as_movable.buckled_mobs)
			guinea_pig.electrocute_act(shock_damage, parent_as_movable)
			break

	parent_as_movable.visible_message(span_danger("The electric chair went off!"), span_hear("You hear a deep sharp shock!"))

///a shock that is toggled manually
/datum/component/electrified_buckle/proc/shock_on_demand()
	SIGNAL_HANDLER
	if((usage_flags & SHOCK_REQUIREMENT_ITEM) && QDELETED(required_object))
		return

	var/atom/movable/parent_as_movable = parent
	if(usage_flags & SHOCK_REQUIREMENT_LIVE_CABLE)
		var/turf/our_turf = get_turf(parent_as_movable)
		var/obj/structure/cable/live_cable = our_turf.get_cable_node()
		if(!live_cable || !live_cable.powernet || live_cable.powernet.avail < ELECTRIC_BUCKLE_MINUMUM_POWERNET_STRENGTH)
			return

		for(var/mob/living/guinea_pig as anything in parent_as_movable.buckled_mobs)
			var/shock_damage = round(live_cable.powernet.avail / ELECTRIC_BUCKLE_SHOCK_STRENGTH_DIVISOR)
			guinea_pig.electrocute_act(shock_damage, parent_as_movable)
			break

/datum/component/electrified_buckle/proc/toggle_shock_loop()
	SIGNAL_HANDLER
	var/atom/movable/parent_as_movable = parent
	if(shock_on_loop)
		shock_on_loop = FALSE
		STOP_PROCESSING(SSprocessing, src)
		parent_as_movable.visible_message(span_notice("The electric chair emits a snap as its circuit opens, making it safe for now."))
	else
		shock_on_loop = TRUE
		START_PROCESSING(SSprocessing, src)
		parent_as_movable.visible_message(span_notice("You hear the sound of an electric circuit closing coming from the electric chair!"))

#undef ELECTRIC_BUCKLE_SHOCK_STRENGTH_DIVISOR
#undef ELECTRIC_BUCKLE_MINUMUM_POWERNET_STRENGTH


code:
/*
	This component is responsible for handling individual instances of embedded objects. The embeddable element is what allows an item to be embeddable and stores its embedding stats,
	and when it impacts and meets the requirements to stick into something, it instantiates an embedded component. Once the item falls out, the component is destroyed, while the
	element survives to embed another day.

		- Carbon embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are:
			-- Every process tick, there is a chance to randomly proc pain, controlled by pain_chance. There may also be a chance for the object to fall out randomly, per fall_chance
			-- Every time the mob moves, there is a chance to proc jostling pain, controlled by jostle_chance (and only 50% as likely if the mob is walking or crawling)
			-- Various signals hooking into carbon topic() and the embed removal surgery in order to handle removals.

	In addition, there are 2 cases of embedding: embedding, and sticking

		- Embedding involves harmful and dangerous embeds, whether they cause brute damage, stamina damage, or a mix. This is the default behavior for embeddings, for when something is "pointy"

		- Sticking occurs when an item should not cause any harm while embedding (imagine throwing a sticky ball of tape at someone, rather than a shuriken). An item is considered "sticky"
			when it has 0 for both pain multiplier and jostle pain multiplier. It's a bit arbitrary, but fairly straightforward.

		Stickables differ from embeds in the following ways:
			-- Text descriptors use phrasing like "X is stuck to Y" rather than "X is embedded in Y"
			-- There is no slicing sound on impact
			-- All damage checks and bloodloss are skipped

*/

/datum/component/embedded
	dupe_mode = COMPONENT_DUPE_ALLOWED
	var/obj/item/bodypart/limb
	var/obj/item/weapon

	// all of this stuff is explained in _DEFINES/combat.dm
	var/embed_chance // not like we really need it once we're already stuck in but hey
	var/fall_chance
	var/pain_chance
	var/pain_mult
	var/impact_pain_mult
	var/remove_pain_mult
	var/rip_time
	var/ignore_throwspeed_threshold
	var/jostle_chance
	var/jostle_pain_mult
	var/pain_stam_pct

	///if both our pain multiplier and jostle pain multiplier are 0, we're harmless and can omit most of the damage related stuff
	var/harmful

/datum/component/embedded/Initialize(obj/item/I,
			datum/thrownthing/throwingdatum,
			obj/item/bodypart/part,
			embed_chance = EMBED_CHANCE,
			fall_chance = EMBEDDED_ITEM_FALLOUT,
			pain_chance = EMBEDDED_PAIN_CHANCE,
			pain_mult = EMBEDDED_PAIN_MULTIPLIER,
			remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
			impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
			rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
			ignore_throwspeed_threshold = FALSE,
			jostle_chance = EMBEDDED_JOSTLE_CHANCE,
			jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER,
			pain_stam_pct = EMBEDDED_PAIN_STAM_PCT)
	if(!iscarbon(parent) || !isitem(I))
		return COMPONENT_INCOMPATIBLE

	if(part)
		limb = part
	src.embed_chance = embed_chance
	src.fall_chance = fall_chance
	src.pain_chance = pain_chance
	src.pain_mult = pain_mult
	src.remove_pain_mult = remove_pain_mult
	src.rip_time = rip_time
	src.impact_pain_mult = impact_pain_mult
	src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
	src.jostle_chance = jostle_chance
	src.jostle_pain_mult = jostle_pain_mult
	src.pain_stam_pct = pain_stam_pct
	src.weapon = I

	if(!weapon.isEmbedHarmless())
		harmful = TRUE

	weapon.embedded(parent, part)
	START_PROCESSING(SSdcs, src)
	var/mob/living/carbon/victim = parent
	limb.embedded_objects |= weapon
	weapon.forceMove(victim)
	RegisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), .proc/weaponDeleted)
	victim.visible_message(span_danger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] [victim]'s [limb.name]!"), span_userdanger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] your [limb.name]!"))

	var/damage = weapon.throwforce
	if(harmful)
		victim.throw_alert("embeddedobject", /atom/movable/screen/alert/embeddedobject)
		playsound(victim,'sound/weapons/bladeslice.ogg', 40)
		weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
		damage += weapon.w_class * impact_pain_mult
		SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)

	if(damage > 0)
		var/armor = victim.run_armor_check(limb.body_zone, MELEE, "Your armor has protected your [limb.name].", "Your armor has softened a hit to your [limb.name].",I.armour_penetration, weak_against_armour = I.weak_against_armour)
		limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, blocked=armor, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness())

/datum/component/embedded/Destroy()
	var/mob/living/carbon/victim = parent
	if(victim && !victim.has_embedded_objects())
		victim.clear_alert("embeddedobject")
		SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded")
	if(weapon)
		UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
	weapon = null
	limb = null
	return ..()

/datum/component/embedded/RegisterWithParent()
	RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/jostleCheck)
	RegisterSignal(parent, COMSIG_CARBON_EMBED_RIP, .proc/ripOut)
	RegisterSignal(parent, COMSIG_CARBON_EMBED_REMOVAL, .proc/safeRemove)
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/checkTweeze)

/datum/component/embedded/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_PARENT_ATTACKBY))

/datum/component/embedded/process(delta_time)
	var/mob/living/carbon/victim = parent

	if(!victim || !limb) // in case the victim and/or their limbs exploded (say, due to a sticky bomb)
		weapon.forceMove(get_turf(weapon))
		qdel(src)
		return

	if(victim.stat == DEAD)
		return

	var/damage = weapon.w_class * pain_mult
	var/pain_chance_current = DT_PROB_RATE(pain_chance / 100, delta_time) * 100
	if(pain_stam_pct && HAS_TRAIT_FROM(victim, TRAIT_INCAPACITATED, STAMINA)) //if it's a less-lethal embed, give them a break if they're already stamcritted
		pain_chance_current *= 0.2
		damage *= 0.5
	else if(victim.body_position == LYING_DOWN)
		pain_chance_current *= 0.2

	if(harmful && prob(pain_chance_current))
		limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND)
		to_chat(victim, span_userdanger("[weapon] embedded in your [limb.name] hurts!"))

	var/fall_chance_current = DT_PROB_RATE(fall_chance / 100, delta_time) * 100
	if(victim.body_position == LYING_DOWN)
		fall_chance_current *= 0.2

	if(prob(fall_chance_current))
		fallOut()

////////////////////////////////////////
////////////BEHAVIOR PROCS//////////////
////////////////////////////////////////

/// Called every time a carbon with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the carbon is crawling or walking.
/datum/component/embedded/proc/jostleCheck()
	SIGNAL_HANDLER

	var/mob/living/carbon/victim = parent
	var/chance = jostle_chance
	if(victim.m_intent == MOVE_INTENT_WALK || victim.body_position == LYING_DOWN)
		chance *= 0.5

	if(harmful && prob(chance))
		var/damage = weapon.w_class * jostle_pain_mult
		/* SEPTIC EDIT REMOVAL
		limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND)
		to_chat(victim, span_userdanger("[weapon] embedded in your [limb.name] jostles and stings!"))
		*/
		//SEPTIC EDIT BEGIN
		if(injury)
			injury.open_injury((1-pain_stam_pct) * damage)
			limb.receive_damage(stamina=pain_stam_pct * damage)
			to_chat(victim, "\The [weapon] embedded in your [limb.name]'s [injury.get_desc()] jostles and stings!")
		else
			limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND)
			to_chat(victim, "\The [weapon] embedded in your [limb.name] jostles and stings!")
		//SEPTIC EDIT END

/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls safe_remove()
/datum/component/embedded/proc/fallOut()
	var/mob/living/carbon/victim = parent

	if(harmful)
		var/damage = weapon.w_class * remove_pain_mult
		/* SEPTIC EDIT REMOVAL
		limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND)
		*/
		//SEPTIC EDIT BEGIN
		if(injury)
			injury.open_injury((1-pain_stam_pct) * damage)
			limb.receive_damage(stamina=pain_stam_pct * damage)
		else
			limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND)
		//SEPTIC EDIT END
	victim.visible_message(span_danger("[weapon] falls [harmful ? "out" : "off"] of [victim.name]'s [limb.name]!"), span_userdanger("[weapon] falls [harmful ? "out" : "off"] of your [limb.name]!"))
	safeRemove()

/// Called when a carbon with an object embedded/stuck to them inspects themselves and clicks the appropriate link to begin ripping the item out. This handles the ripping attempt, descriptors, and dealing damage, then calls safe_remove()
/datum/component/embedded/proc/ripOut(datum/source, obj/item/I, obj/item/bodypart/limb)
	SIGNAL_HANDLER

	if(I != weapon || src.limb != limb)
		return
	var/mob/living/carbon/victim = parent
	var/time_taken = rip_time * weapon.w_class
	INVOKE_ASYNC(src, .proc/complete_rip_out, victim, I, limb, time_taken)

/// everything async that ripOut used to do
/datum/component/embedded/proc/complete_rip_out(mob/living/carbon/victim, obj/item/I, obj/item/bodypart/limb, time_taken)
	victim.visible_message(span_warning("[victim] attempts to remove [weapon] from [victim.p_their()] [limb.name]."),span_notice("You attempt to remove [weapon] from your [limb.name]... (It will take [DisplayTimeText(time_taken)].)"))
	if(!do_after(victim, time_taken, target = victim))
		return
	if(!weapon || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects))
		qdel(src)
		return
	if(harmful)
		var/damage = weapon.w_class * remove_pain_mult
		limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, sharpness=SHARP_EDGED) //It hurts to rip it out, get surgery you dingus. unlike the others, this CAN wound + increase slash bloodflow
		victim.emote("scream")

	victim.visible_message(span_notice("[victim] successfully rips [weapon] [harmful ? "out" : "off"] of [victim.p_their()] [limb.name]!"), span_notice("You successfully remove [weapon] from your [limb.name]."))
	safeRemove(victim)

/// This proc handles the final step and actual removal of an embedded/stuck item from a carbon, whether or not it was actually removed safely.
/// If you want the thing to go into someone's hands rather than the floor, pass them in to_hands
/datum/component/embedded/proc/safeRemove(mob/to_hands)
	SIGNAL_HANDLER

	var/mob/living/carbon/victim = parent
	limb.embedded_objects -= weapon
	UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) // have to do it here otherwise we trigger weaponDeleted()
	if(!weapon.unembedded()) // if it hasn't deleted itself due to drop del
		UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
		if(to_hands)
			INVOKE_ASYNC(to_hands, /mob.proc/put_in_hands, weapon)
		else
			weapon.forceMove(get_turf(victim))

	qdel(src)

/// Something deleted or moved our weapon while it was embedded, how rude!
/datum/component/embedded/proc/weaponDeleted()
	SIGNAL_HANDLER

	var/mob/living/carbon/victim = parent
	/* SEPTIC EDIT REMOVAL
	limb.embedded_objects -= weapon
	*/
	//SEPTIC EDIT BEGIN
	LAZYREMOVE(limb.embedded_objects, weapon)
	if(injury)
		LAZYREMOVE(injury.embedded_objects, weapon)
		LAZYREMOVE(injury.embedded_components, src)
	//SEPTIC EDIT END

	if(victim)
		to_chat(victim, span_userdanger("\The [weapon] that was embedded in your [limb.name] disappears!"))

	qdel(src)

/// The signal for listening to see if someone is using a hemostat on us to pluck out this object
/datum/component/embedded/proc/checkTweeze(mob/living/carbon/victim, obj/item/possible_tweezers, mob/user)
	SIGNAL_HANDLER

	if(!istype(victim) || possible_tweezers.tool_behaviour != TOOL_HEMOSTAT || user.zone_selected != limb.body_zone)
		return
	/* SEPTIC EDIT REMOVAL
	if(weapon != limb.embedded_objects[1]) // just pluck the first one, since we can't easily coordinate with other embedded components affecting this limb who is highest priority
		return
	*/
	//SEPTIC EDIT BEGIN
	if(weapon != LAZYACCESS(limb.embedded_objects, 1)) // just pluck the first one, since we can't easily coordinate with other embedded components affecting this limb who is highest priority
		return
	//SEPTIC EDIT END
	if(ishuman(victim)) // check to see if the limb is actually exposed
		var/mob/living/carbon/human/victim_human = victim
		if(!victim_human.try_inject(user, limb.body_zone, INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE))
			return TRUE

	INVOKE_ASYNC(src, .proc/tweezePluck, possible_tweezers, user)
	return COMPONENT_NO_AFTERATTACK

/// The actual action for pulling out an embedded object with a hemostat
/datum/component/embedded/proc/tweezePluck(obj/item/possible_tweezers, mob/user)
	var/mob/living/carbon/victim = parent

	var/self_pluck = (user == victim)

	if(self_pluck)
		user.visible_message(span_danger("[user] begins plucking [weapon] from [user.p_their()] [limb.name]"), span_notice("You start plucking [weapon] from your [limb.name]..."),\
			vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim)
	else
		user.visible_message(span_danger("[user] begins plucking [weapon] from [victim]'s [limb.name]"),span_notice("You start plucking [weapon] from [victim]'s [limb.name]..."), \
			vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim)
		to_chat(victim, span_userdanger("[user] begins plucking [weapon] from your [limb.name]..."))

	var/pluck_time = 2.5 SECONDS * weapon.w_class * (self_pluck ? 2 : 1)
	if(!do_after(user, pluck_time, victim))
		if(self_pluck)
			to_chat(user, span_danger("You fail to pluck [weapon] from your [limb.name]."))
		else
			to_chat(user, span_danger("You fail to pluck [weapon] from [victim]'s [limb.name]."))
			to_chat(victim, span_danger("[user] fails to pluck [weapon] from your [limb.name]."))
		return

	to_chat(user, span_notice("You successfully pluck [weapon] from [victim]'s [limb.name]."))
	to_chat(victim, span_notice("[user] plucks [weapon] from your [limb.name]."))
	safeRemove(user)


code:
/**
 * # engraved component!
 *
 * component for walls that applies an engraved overlay and lets you examine it to read a story (+ art element yay)
 * new creations will get a high art value, cross round scrawlings will get a low one.
 * MUST be a component, though it doesn't look like it. SSPersistence demandeth
 */
/datum/component/engraved
	///the generated story string
	var/engraved_description
	///whether this is a new engraving, or a persistence loaded one.
	var/persistent_save
	///what random icon state should the engraving have
	var/icon_state_append
	///The story value of this piece.
	var/story_value

/datum/component/engraved/Initialize(engraved_description, persistent_save, story_value)
	. = ..()
	if(!isclosedturf(parent))
		return COMPONENT_INCOMPATIBLE
	var/turf/closed/engraved_wall = parent

	src.engraved_description = engraved_description
	src.persistent_save = persistent_save
	src.story_value = story_value

	var/beauty_value
	switch(story_value)
		if(STORY_VALUE_SHIT)
			beauty_value = rand(-50, 50) //Ugly or mediocre at best
		if(STORY_VALUE_NONE)
			beauty_value = rand(0, 100) //No inherent value
		if(STORY_VALUE_MEH)
			beauty_value = rand(100, 200) //Its an okay tale
		if(STORY_VALUE_OKAY)
			beauty_value = rand(150, 300) //Average story! most things are like this
		if(STORY_VALUE_AMAZING)
			beauty_value = rand(300, 600)//Really impactful stories, seeing a lost limb, losing a loved pet.
		if(STORY_VALUE_LEGENDARY)
			beauty_value = rand(500, 800) //Almost always a good story! this is for memories you can barely ever get, killing megafauna, doing ultimate feats!

	engraved_wall.AddElement(/datum/element/art, beauty_value / ENGRAVING_BEAUTY_TO_ART_FACTOR)
	if(persistent_save)
		engraved_wall.AddElement(/datum/element/beauty, beauty_value)
	else
		engraved_wall.AddElement(/datum/element/beauty, beauty_value / ENGRAVING_PERSISTENCE_BEAUTY_LOSS_FACTOR) //Old age does them harm
	icon_state_append = rand(1, 2)
	//must be here to allow overlays to be updated
	RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, .proc/on_update_overlays)
	engraved_wall.update_appearance()

/datum/component/engraved/Destroy(force, silent)
	. = ..()
	parent.RemoveElement(/datum/element/art)
	//must be here to allow overlays to be updated
	UnregisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS)
	if(parent && !QDELING(parent))
		var/atom/parent_atom = parent
		parent_atom.update_appearance()

/datum/component/engraved/RegisterWithParent()
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	//supporting component transfer means putting these here instead of initialize
	SSpersistence.wall_engravings += src
	ADD_TRAIT(parent, TRAIT_NOT_ENGRAVABLE, TRAIT_GENERIC)

/datum/component/engraved/UnregisterFromParent()
	UnregisterSignal(parent, COMSIG_PARENT_EXAMINE)
	//supporting component transfer means putting these here instead of destroy
	SSpersistence.wall_engravings -= src
	REMOVE_TRAIT(parent, TRAIT_NOT_ENGRAVABLE, TRAIT_GENERIC)

/// Used to maintain the acid overlay on the parent [/atom].
/datum/component/engraved/proc/on_update_overlays(atom/parent_atom, list/overlays)
	SIGNAL_HANDLER

	overlays += mutable_appearance('icons/turf/wall_overlays.dmi', "engraving[icon_state_append]")

///signal called on parent being examined
/datum/component/engraved/proc/on_examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER
	examine_list += span_boldnotice(engraved_description)

///returns all the information SSpersistence needs in a list to load up this engraving on a future round!
/datum/component/engraved/proc/save_persistent()
	var/list/saved_data = list()
	saved_data["story"] = engraved_description
	saved_data["story_value"] = story_value

	return list(saved_data)


code:
///Component specifically for explosion sensetive things, currently only applies to heat based explosions but can later perhaps be used for things that are dangerous to handle carelessly like nitroglycerin.
/datum/component/explodable
	/// The devastation range of the resulting explosion.
	var/devastation_range = 0
	/// The heavy impact range of the resulting explosion.
	var/heavy_impact_range = 0
	/// The light impact range of the resulting explosion.
	var/light_impact_range = 2
	/// The flame range of the resulting explosion.
	var/flame_range = 0
	/// The flash range of the resulting explosion.
	var/flash_range = 3
	/// For items, lets us determine where things should be hit.
	var/equipped_slot
	/// Whether we always delete. Useful for nukes turned plasma and such, so they don't default delete and can survive
	var/always_delete

/datum/component/explodable/Initialize(devastation_range_override, heavy_impact_range_override, light_impact_range_override, flame_range_override, flash_range_override, _always_delete = TRUE)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/explodable_attack)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/explodable_insert_item)
	RegisterSignal(parent, COMSIG_ATOM_EX_ACT, .proc/detonate)
	if(ismovable(parent))
		RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, .proc/explodable_impact)
		RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/explodable_bump)
		if(isitem(parent))
			RegisterSignal(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_ATTACK_OBJ, COMSIG_ITEM_HIT_REACT), .proc/explodable_attack)
			RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
			RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)

	if(devastation_range_override)
		devastation_range = devastation_range_override
	if(heavy_impact_range_override)
		heavy_impact_range = heavy_impact_range_override
	if(light_impact_range_override)
		light_impact_range = light_impact_range_override
	if(flame_range_override)
		flame_range = flame_range_override
	if(flash_range_override)
		flash_range = flash_range_override
	always_delete = _always_delete

/datum/component/explodable/proc/explodable_insert_item(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE)
	SIGNAL_HANDLER

	check_if_detonate(I)

/datum/component/explodable/proc/explodable_impact(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum)
	SIGNAL_HANDLER

	check_if_detonate(hit_atom)

/datum/component/explodable/proc/explodable_bump(datum/source, atom/A)
	SIGNAL_HANDLER

	check_if_detonate(A)

///Called when you use this object to attack sopmething
/datum/component/explodable/proc/explodable_attack(datum/source, atom/movable/target, mob/living/user)
	SIGNAL_HANDLER

	check_if_detonate(target)

///Called when you attack a specific body part of the thing this is equipped on. Useful for exploding pants.
/datum/component/explodable/proc/explodable_attack_zone(datum/source, damage, damagetype, def_zone)
	SIGNAL_HANDLER

	if(!def_zone)
		return
	if(damagetype != BURN) //Don't bother if it's not fire.
		return
	if(!is_hitting_zone(def_zone)) //You didn't hit us! ha!
		return
	detonate()

/datum/component/explodable/proc/on_equip(datum/source, mob/equipper, slot)
	SIGNAL_HANDLER

	RegisterSignal(equipper, COMSIG_MOB_APPLY_DAMAGE,  .proc/explodable_attack_zone, TRUE)

/datum/component/explodable/proc/on_drop(datum/source, mob/user)
	SIGNAL_HANDLER

	UnregisterSignal(user, COMSIG_MOB_APPLY_DAMAGE)

/// Checks if we're hitting the zone this component is covering
/datum/component/explodable/proc/is_hitting_zone(def_zone)
	var/obj/item/item = parent
	var/mob/living/L = item.loc //Get whoever is equipping the item currently

	if(!istype(L))
		return

	var/obj/item/bodypart/bodypart = L.get_bodypart(check_zone(def_zone))

	var/list/equipment_items = list()
	if(iscarbon(L))
		var/mob/living/carbon/C = L
		equipment_items += list(C.head, C.wear_mask, C.back, C.gloves, C.shoes, C.glasses, C.ears)
		if(ishuman(C))
			var/mob/living/carbon/human/H = C
			equipment_items += list(H.wear_suit, H.w_uniform, H.belt, H.s_store, H.wear_id)

	for(var/bp in equipment_items)
		if(!bp)
			continue

		var/obj/item/I = bp
		if(I.body_parts_covered & bodypart.body_part)
			return TRUE
	return FALSE

/datum/component/explodable/proc/check_if_detonate(target)
	if(!isitem(target))
		return
	var/obj/item/I = target
	if(!I.get_temperature())
		return
	detonate() //If we're touching a hot item we go boom

/// Explode and remove the object
/datum/component/explodable/proc/detonate()
	SIGNAL_HANDLER

	var/atom/A = parent
	var/log = TRUE
	if(light_impact_range < 1)
		log = FALSE
	explosion(A, devastation_range, heavy_impact_range, light_impact_range, flame_range, flash_range, log) //epic explosion time
	if(always_delete)
		qdel(A)


code:
/**
 * ## faction granter component!
 *
 * component attached to items, lets them be used in hand once to add yourself to a certain faction
 * one good example is the chaplain plushie that grants you the carp faction, making you friendly with them.
 */
/datum/component/faction_granter
	dupe_mode = COMPONENT_DUPE_ALLOWED
	///whichever faction the parent adds upon using in hand
	var/faction_to_grant
	///whether you need to be holy to get the faction.
	var/holy_role_required
	///message given when granting the faction.
	var/grant_message
	///boolean on whether it has been used
	var/used = FALSE

/datum/component/faction_granter/Initialize(faction_to_grant, holy_role_required = NONE, grant_message)
	if(!isitem(parent))
		return COMPONENT_INCOMPATIBLE
	if(!grant_message) //Yes we could do this in the init call with default args, but it scares people
		grant_message = "You have become friends with [faction_to_grant]"
	src.faction_to_grant = faction_to_grant
	src.holy_role_required = holy_role_required
	src.grant_message = grant_message

/datum/component/faction_granter/RegisterWithParent()
	RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/on_self_attack)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)

/datum/component/faction_granter/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_PARENT_EXAMINE))

///signal called on parent being examined
/datum/component/faction_granter/proc/on_examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER
	if(used)
		examine_list += span_notice("[parent]'s favor granting power has been used up.")
	else
		examine_list += span_notice("Using [parent] in your hand will grant you favor with [faction_to_grant]\s")

///signal called on parent being interacted with in hand
/datum/component/faction_granter/proc/on_self_attack(atom/source, mob/user)
	SIGNAL_HANDLER
	if(used)
		to_chat(user, span_warning("The power of [parent] has been used up!"))
		return
	if(user.mind?.holy_role >= holy_role_required)
		to_chat(user, span_warning("You are not holy enough to invoke the power of [parent]!"))
		return

	to_chat(user, grant_message)
	user.faction |= faction_to_grant
	used = TRUE


code:
/datum/component/fantasy
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS

	var/quality

	var/canFail
	var/announce

	var/originalName
	var/list/affixes
	var/list/appliedComponents

	var/static/list/affixListing

///affixes expects an initialized list
/datum/component/fantasy/Initialize(quality, list/affixes = list(), canFail=FALSE, announce=FALSE)
	if(!isitem(parent))
		return COMPONENT_INCOMPATIBLE

	src.quality = quality || randomQuality()
	src.canFail = canFail
	src.announce = announce

	src.affixes = affixes
	appliedComponents = list()
	if(affixes && affixes.len)
		setAffixes()
	else
		randomAffixes()

/datum/component/fantasy/Destroy()
	unmodify()
	affixes = null
	return ..()

/datum/component/fantasy/RegisterWithParent()
	var/obj/item/master = parent
	originalName = master.name
	modify()

/datum/component/fantasy/UnregisterFromParent()
	unmodify()

/datum/component/fantasy/InheritComponent(datum/component/fantasy/newComp, original, quality, list/affixes, canFail, announce)
	unmodify()
	if(newComp)
		src.quality += newComp.quality
		src.canFail = newComp.canFail
		src.announce = newComp.announce
	else
		src.quality += quality
		src.canFail = canFail || src.canFail
		src.announce = announce || src.announce
	modify()

/datum/component/fantasy/proc/randomQuality()
	var/quality = pick(1;15, 2;14, 2;13, 2;12, 3;11, 3;10, 3;9, 4;8, 4;7, 4;6, 5;5, 5;4, 5;3, 6;2, 6;1, 6;0)
	if(prob(50))
		quality = -quality
	return quality

///proc on creation for random affixes
/datum/component/fantasy/proc/randomAffixes(force)
	if(!affixListing)
		affixListing = list()
		for(var/T in subtypesof(/datum/fantasy_affix))
			var/datum/fantasy_affix/affix = new T
			affixListing[affix] = affix.weight

	if(length(affixes))
		if(!force)
			return
		affixes = list()

	var/alignment
	if(quality >= 0)
		alignment |= AFFIX_GOOD
	if(quality <= 0)
		alignment |= AFFIX_EVIL

	var/usedSlots = NONE
	for(var/i in 1 to max(1, abs(quality))) // We want at least 1 affix applied
		var/datum/fantasy_affix/affix = pick_weight(affixListing)
		if(affix.placement & usedSlots)
			continue
		if(!(affix.alignment & alignment))
			continue
		if(!affix.validate(parent))
			continue
		affixes += affix
		usedSlots |= affix.placement

///proc on creation for specific affixes given to the fantasy component
/datum/component/fantasy/proc/setAffixes(force)
	var/usedSlots = NONE
	for(var/datum/fantasy_affix/affix in affixes) // We want at least 1 affix applied
		if((affix.placement & usedSlots) || (!affix.validate(parent)))
			affixes.Remove(affix) //bad affix (can't be added to this item)
			continue
		usedSlots |= affix.placement

/datum/component/fantasy/proc/modify()
	var/obj/item/master = parent

	master.force = max(0, master.force + quality)
	master.throwforce = max(0, master.throwforce + quality)
	master.armor = master.armor?.modifyAllRatings(quality)
	master.wound_bonus += quality
	master.bare_wound_bonus += quality

	var/newName = originalName
	for(var/i in affixes)
		var/datum/fantasy_affix/affix = i
		newName = affix.apply(src, newName)

	if(quality != 0)
		newName = "[newName] [quality > 0 ? "+" : ""][quality]"

	if(canFail && prob((quality - 9)*10))
		var/turf/place = get_turf(parent)
		place.visible_message(span_danger("[parent] [span_blue("violently glows blue")] for a while, then evaporates."))
		master.burn()
		return
	else if(announce)
		announce()

	master.name = newName

/datum/component/fantasy/proc/unmodify()
	var/obj/item/master = parent

	for(var/i in affixes)
		var/datum/fantasy_affix/affix = i
		affix.remove(src)
	QDEL_LIST(appliedComponents)

	master.force = max(0, master.force - quality)
	master.throwforce = max(0, master.throwforce - quality)
	master.armor = master.armor?.modifyAllRatings(-quality)
	master.wound_bonus -= quality
	master.bare_wound_bonus -= quality

	master.name = originalName

/datum/component/fantasy/proc/announce()
	var/turf/location = get_turf(parent)
	var/span
	var/effect_description
	if(quality >= 0)
		span = ""
		effect_description = "shimmering golden glow"
	else
		span = ""
		effect_description = span_bold("mottled black glow")

	location.visible_message("[span][originalName] is covered by a [effect_description] and then transforms into [parent]!")


code:
/datum/fantasy_affix
	///only used for admins adding the affix, this is not what players will see.
	var/name = "SOMEONE DIDN'T SET AN ADMIN NAME FOR THIS"
	var/placement // A bitflag of "slots" this affix takes up, for example pre/suffix
	var/alignment
	var/weight = 10

// For those occasional affixes which only make sense in certain circumstances
/datum/fantasy_affix/proc/validate(obj/item/attached)
	return TRUE

/datum/fantasy_affix/proc/apply(datum/component/fantasy/comp, newName)
	return newName

/datum/fantasy_affix/proc/remove(datum/component/fantasy/comp)


code:
/datum/fantasy_affix/cosmetic_prefixes
	name = "purely cosmetic prefix"
	placement = AFFIX_PREFIX
	alignment = AFFIX_GOOD | AFFIX_EVIL

	var/list/goodPrefixes
	var/list/badPrefixes

/datum/fantasy_affix/cosmetic_prefixes/New()
	goodPrefixes = list(
		"greater",
		"major",
		"blessed",
		"superior",
		"empowered",
		"honed",
		"true",
		"glorious",
		"robust",
		)
	badPrefixes = list(
		"lesser",
		"minor",
		"blighted",
		"inferior",
		"enfeebled",
		"rusted",
		"unsteady",
		"tragic",
		"gimped",
		"cursed",
		)

	weight = (length(goodPrefixes) + length(badPrefixes)) * 10

/datum/fantasy_affix/cosmetic_prefixes/apply(datum/component/fantasy/comp, newName)
	if(comp.quality > 0 || (comp.quality == 0 && prob(50)))
		return "[pick(goodPrefixes)] [newName]"
	else
		return "[pick(badPrefixes)] [newName]"

/datum/fantasy_affix/tactical
	name = "tactical"
	placement = AFFIX_PREFIX
	alignment = AFFIX_GOOD
	weight = 1 // Very powerful, no one should have such power

/datum/fantasy_affix/tactical/apply(datum/component/fantasy/comp, newName)
	var/obj/item/master = comp.parent
	comp.appliedComponents += master.AddComponent(/datum/component/tactical)
	return "tactical [newName]"

/datum/fantasy_affix/pyromantic
	name = "pyromantic"
	placement = AFFIX_PREFIX
	alignment = AFFIX_GOOD

/datum/fantasy_affix/pyromantic/apply(datum/component/fantasy/comp, newName)
	var/obj/item/master = comp.parent
	comp.appliedComponents += master.AddComponent(/datum/component/igniter, clamp(comp.quality, 1, 10))
	return "pyromantic [newName]"

/datum/fantasy_affix/vampiric
	name = "vampiric"
	placement = AFFIX_PREFIX
	alignment = AFFIX_GOOD
	weight = 5

/datum/fantasy_affix/vampiric/validate(obj/item/attached)
	return attached.force //don't apply to things that just bap people

/datum/fantasy_affix/vampiric/apply(datum/component/fantasy/comp, newName)
	var/obj/item/master = comp.parent
	master.AddElement(/datum/element/lifesteal, comp.quality)
	return "vampiric [newName]"

/datum/fantasy_affix/vampiric/remove(datum/component/fantasy/comp)
	var/obj/item/master = comp.parent
	master.RemoveElement(/datum/element/lifesteal, comp.quality)

/datum/fantasy_affix/beautiful
	name = "beautiful"
	placement = AFFIX_PREFIX
	alignment = AFFIX_GOOD

/datum/fantasy_affix/beautiful/apply(datum/component/fantasy/comp, newName)
	var/obj/item/master = comp.parent
	master.AddElement(/datum/element/beauty, max(comp.quality, 1) * 250)
	return "[pick("aesthetic", "beautiful", "gorgeous", "pretty")] [newName]"

/datum/fantasy_affix/beautiful/remove(datum/component/fantasy/comp)
	var/obj/item/master = comp.parent
	master.RemoveElement(/datum/element/beauty, max(comp.quality, 1) * 250)

/datum/fantasy_affix/ugly
	name = "ugly"
	placement = AFFIX_PREFIX
	alignment = AFFIX_EVIL

/datum/fantasy_affix/ugly/apply(datum/component/fantasy/comp, newName)
	var/obj/item/master = comp.parent
	master.AddElement(/datum/element/beauty, min(comp.quality, -1) * 250)
	return "[pick("fugly", "ugly", "grotesque", "hideous")] [newName]"

/datum/fantasy_affix/ugly/remove(datum/component/fantasy/comp)
	var/obj/item/master = comp.parent
	master.RemoveElement(/datum/element/beauty, min(comp.quality, -1) * 250)

/datum/fantasy_affix/venomous
	name = "<poisonname>-laced (picked from small pool of toxins)"
	placement = AFFIX_PREFIX
	alignment = AFFIX_GOOD

/datum/fantasy_affix/venomous/validate(obj/item/attached)
	return attached.force //don't apply to things that just bap people

/datum/fantasy_affix/venomous/apply(datum/component/fantasy/comp, newName)
	var/obj/item/master = comp.parent
	var/picked_poison = pick(list(
		/datum/reagent/toxin/plantbgone,
		/datum/reagent/toxin/mutetoxin,
		/datum/reagent/toxin/amanitin,
		/datum/reagent/toxin/lipolicide,
		/datum/reagent/toxin/spewium,
		/datum/reagent/toxin/heparin,
		/datum/reagent/toxin/rotatium,
		/datum/reagent/toxin/histamine
	))
	var/list/names = list(
		/datum/reagent/toxin/plantbgone = "plantbane",
		/datum/reagent/toxin/mutetoxin = "mimemind",
		/datum/reagent/toxin/amanitin = "dormant death",
		/datum/reagent/toxin/lipolicide = "famineblood",
		/datum/reagent/toxin/spewium = "gulchergut",
		/datum/reagent/toxin/heparin = "jabberwound",
		/datum/reagent/toxin/rotatium = "spindown",
		/datum/reagent/toxin/histamine = "creeping malaise"
	)
	var/poisonname = names[picked_poison]
	master.AddElement(/datum/element/venomous, picked_poison, comp.quality+1)
	//seriously don't @ me about the correct use of venom vs poison. shut up.
	return "[poisonname]-[pick("poisoned", "envenomed", "laced")] [newName]"

/datum/fantasy_affix/venomous/remove(datum/component/fantasy/comp)
	var/obj/item/master = comp.parent
	master.RemoveElement(/datum/element/venomous)


code:
/datum/fantasy_affix/cosmetic_suffixes
	name = "purely cosmetic suffix"
	placement = AFFIX_SUFFIX
	alignment = AFFIX_GOOD | AFFIX_EVIL

	var/list/goodSuffixes
	var/list/badSuffixes

/datum/fantasy_affix/cosmetic_suffixes/New()
	goodSuffixes = list(
		"dexterity",
		"constitution",
		"intelligence",
		"wisdom",
		"charisma",
		"the forest",
		"the hills",
		"the plains",
		"the sea",
		"the sun",
		"the moon",
		"the void",
		"the world",
		"many secrets",
		"many tales",
		"many colors",
		"rending",
		"sundering",
		"the night",
		"the day",
		)
	badSuffixes = list(
		"draining",
		"burden",
		"discomfort",
		"awkwardness",
		"poor hygiene",
		"timidity",
		)

	weight = (length(goodSuffixes) + length(badSuffixes)) * 10

/datum/fantasy_affix/cosmetic_suffixes/apply(datum/component/fantasy/comp, newName)
	if(comp.quality > 0 || (comp.quality == 0 && prob(50)))
		return "[newName] of [pick(goodSuffixes)]"
	else
		return "[newName] of [pick(badSuffixes)]"

//////////// Good suffixes
/datum/fantasy_affix/bane
	name = "of <mobtype> slaying (random species, carbon or simple animal)"
	placement = AFFIX_SUFFIX
	alignment = AFFIX_GOOD
	var/list/target_types_by_comp = list()

/datum/fantasy_affix/bane/apply(datum/component/fantasy/comp, newName)
	. = ..()
	// This is set up to be easy to add to these lists as I expect it will need modifications
	var/static/list/possible_mobtypes
	if(!possible_mobtypes)
		// The base list of allowed mob/species types
		possible_mobtypes = typecacheof(list(
			/mob/living/simple_animal,
			/mob/living/carbon,
			/datum/species,
			))
		// Some particular types to disallow if they're too broad/abstract
		possible_mobtypes -= list(
			/mob/living/simple_animal/hostile,
			)
		// Some types to remove them and their subtypes
		possible_mobtypes -= typecacheof(list(
			/mob/living/carbon/human/species,
			))

	var/mob/picked_mobtype = pick(possible_mobtypes)
	// This works even with the species picks since we're only accessing the name

	var/obj/item/master = comp.parent
	master.AddElement(/datum/element/bane, picked_mobtype)
	target_types_by_comp[comp] = picked_mobtype
	return "[newName] of [initial(picked_mobtype.name)] slaying"

/datum/fantasy_affix/bane/remove(datum/component/fantasy/comp)
	var/picked_mobtype = target_types_by_comp[comp]
	var/obj/item/master = comp.parent
	master.RemoveElement(/datum/element/bane, picked_mobtype)
	target_types_by_comp -= comp

/datum/fantasy_affix/summoning
	name = "of <mobtype> summoning (dangerous, can pick all but megafauna tier stuff)"
	placement = AFFIX_SUFFIX
	alignment = AFFIX_GOOD
	weight = 5

/datum/fantasy_affix/summoning/apply(datum/component/fantasy/comp, newName)
	. = ..()
	// This is set up to be easy to add to these lists as I expect it will need modifications
	var/static/list/possible_mobtypes
	if(!possible_mobtypes)
		// The base list of allowed mob/species types
		possible_mobtypes = typecacheof(list(
			/mob/living/simple_animal,
			/mob/living/carbon,
			/datum/species,
			))
		// Some particular types to disallow if they're too broad/abstract
		possible_mobtypes -= list(
			/mob/living/simple_animal/hostile,
			)
		// Some types to remove them and their subtypes
		possible_mobtypes -= typecacheof(list(
			/mob/living/carbon/human/species,
			/mob/living/simple_animal/hostile/syndicate/mecha_pilot,
			/mob/living/simple_animal/hostile/asteroid/elite,
			/mob/living/simple_animal/hostile/megafauna,
			))

	var/mob/picked_mobtype = pick(possible_mobtypes)
	// This works even with the species picks since we're only accessing the name

	var/obj/item/master = comp.parent
	var/max_mobs = max(CEILING(comp.quality/2, 1), 1)
	var/spawn_delay = 300 - 30 * comp.quality
	comp.appliedComponents += master.AddComponent(/datum/component/summoning, list(picked_mobtype), 100, max_mobs, spawn_delay)
	return "[newName] of [initial(picked_mobtype.name)] summoning"

/datum/fantasy_affix/shrapnel
	name = "shrapnel"
	placement = AFFIX_SUFFIX
	alignment = AFFIX_GOOD

/datum/fantasy_affix/shrapnel/validate(obj/item/attached)
	if(isgun(attached))
		return TRUE
	return FALSE

/datum/fantasy_affix/shrapnel/apply(datum/component/fantasy/comp, newName)
	. = ..()
	// higher means more likely
	var/list/weighted_projectile_types = list(/obj/projectile/meteor = 1,
											  /obj/projectile/energy/nuclear_particle = 1,
											  /obj/projectile/beam/pulse = 1,
											  /obj/projectile/bullet/honker = 15,
											  /obj/projectile/temp = 15,
											  /obj/projectile/ion = 15,
											  /obj/projectile/magic/door = 15,
											  /obj/projectile/magic/locker = 15,
											  /obj/projectile/magic/fetch = 15,
											  /obj/projectile/beam/emitter = 15,
											  /obj/projectile/magic/flying = 15,
											  /obj/projectile/energy/net = 15,
											  /obj/projectile/bullet/incendiary/c9mm = 15,
											  /obj/projectile/temp/hot = 15,
											  /obj/projectile/beam/disabler = 15)

	var/obj/projectile/picked_projectiletype = pick_weight(weighted_projectile_types)

	var/obj/item/master = comp.parent
	comp.appliedComponents += master.AddComponent(/datum/component/mirv, picked_projectiletype)
	return "[newName] of [initial(picked_projectiletype.name)] shrapnel"

/datum/fantasy_affix/strength
	name = "of strength (knockback)"
	placement = AFFIX_SUFFIX
	alignment = AFFIX_GOOD

/datum/fantasy_affix/strength/apply(datum/component/fantasy/comp, newName)
	. = ..()
	var/obj/item/master = comp.parent
	master.AddElement(/datum/element/knockback, CEILING(comp.quality/2, 1), FLOOR(comp.quality/10, 1))
	return "[newName] of strength"

/datum/fantasy_affix/strength/remove(datum/component/fantasy/comp)
	var/obj/item/master = comp.parent
	master.RemoveElement(/datum/element/knockback, CEILING(comp.quality/2, 1), FLOOR(comp.quality/10, 1))

//////////// Bad suffixes

/datum/fantasy_affix/fool
	name = "of the fool (honking)"
	placement = AFFIX_SUFFIX
	alignment = AFFIX_EVIL

/datum/fantasy_affix/fool/apply(datum/component/fantasy/comp, newName)
	. = ..()
	var/obj/item/master = comp.parent
	comp.appliedComponents += master.AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg' = 1), 50, falloff_exponent = 20)
	return "[newName] of the fool"

/datum/fantasy_affix/curse_of_hunger
	name = "curse of hunger"
	placement = AFFIX_SUFFIX
	alignment = AFFIX_EVIL

/datum/fantasy_affix/curse_of_hunger/validate(obj/item/attached)
	//curse of hunger that attaches onto food has the ability to eat itself. it's hilarious.
	if(!IS_EDIBLE(attached))
		return TRUE
	return TRUE

/datum/fantasy_affix/curse_of_hunger/apply(datum/component/fantasy/comp, newName)
	. = ..()
	var/obj/item/master = comp.parent
	var/filter_color = "#8a0c0ca1" //clarified args
	var/new_name = pick(", eternally hungry", " of the glutton", " cursed with hunger", ", consumer of all", " of the feast")
	master.AddElement(/datum/element/curse_announcement, "[master] is cursed with the curse of hunger!", filter_color, new_name, comp)
	var/add_dropdel = FALSE //clarified boolean
	comp.appliedComponents += master.AddComponent(/datum/component/curse_of_hunger, add_dropdel)
	return newName //no spoilers!

/datum/fantasy_affix/curse_of_hunger/remove(datum/component/fantasy/comp)
	var/obj/item/master = comp.parent
	master.RemoveElement(/datum/element/curse_announcement) //just in case


code:
//"Don't leave food on the floor, that's how we get ants"

#define DECOMPOSITION_TIME 10 MINUTES
#define DECOMPOSITION_TIME_RAW 5 MINUTES
#define DECOMPOSITION_TIME_GROSS 7 MINUTES

#define DECOMP_EXAM_NORMAL 0
#define DECOMP_EXAM_GROSS 1
#define DECOMP_EXAM_RAW 2

/datum/component/decomposition
	dupe_mode = COMPONENT_DUPE_UNIQUE
	/// Makes sure maploaded food only starts decomposing if a player's EVER picked it up before
	var/handled = TRUE
	/// Used to stop food in someone's hand & in storage slots from decomposing.
	var/protected = FALSE
	/// Used to stop the timer & check for the examine proc
	var/timerid
	/// Used so the timer won't reset.
	var/time_remaining = DECOMPOSITION_TIME
	/// Used to give raw/gross food lower timers
	var/decomp_flags
	/// Use for determining what kind of item the food decomposes into.
	var/decomp_result
	/// Does our food attract ants?
	var/produce_ants = TRUE
	/// Used for examining
	var/examine_type = DECOMP_EXAM_NORMAL

/datum/component/decomposition/Initialize(mapload, decomp_req_handle, decomp_flags = NONE, decomp_result, ant_attracting = TRUE)
	if(!isobj(parent))
		return COMPONENT_INCOMPATIBLE

	src.decomp_flags = decomp_flags
	src.decomp_result = decomp_result
	if(mapload || decomp_req_handle)
		handled = FALSE
	if(!ant_attracting)
		produce_ants = FALSE

	RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/handle_movement)
	RegisterSignal(parent, list(
		COMSIG_ITEM_PICKUP, //person picks up an item
		COMSIG_STORAGE_ENTERED), //Object enters a storage object (boxes, etc.)
		.proc/picked_up)
	RegisterSignal(parent, list(
		COMSIG_ITEM_DROPPED, //Object is dropped anywhere
		COMSIG_STORAGE_EXITED), //Object exits a storage object (boxes, etc)
		.proc/dropped)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)

	if(decomp_flags & RAW) // Raw food overrides gross
		time_remaining = DECOMPOSITION_TIME_RAW
		examine_type = DECOMP_EXAM_RAW
	else if(decomp_flags & GROSS)
		time_remaining = DECOMPOSITION_TIME_GROSS
		examine_type = DECOMP_EXAM_GROSS

	handle_movement()

/datum/component/decomposition/UnregisterFromParent()
	UnregisterSignal(parent, list(
		COMSIG_ITEM_PICKUP,
		COMSIG_STORAGE_ENTERED,
		COMSIG_MOVABLE_MOVED,
		COMSIG_ITEM_DROPPED,
		COMSIG_STORAGE_EXITED,
		COMSIG_PARENT_EXAMINE))

/datum/component/decomposition/proc/handle_movement()
	SIGNAL_HANDLER
	if(!handled) // If maploaded, has someone touched this previously?
		return
	var/obj/food = parent // Doesn't HAVE to be food, that's just what it's intended for

	var/turf/open/open_turf = food.loc

	if(!istype(open_turf)) //Are we actually in an open turf?
		remove_timer()
		return

	for(var/atom/movable/content as anything in open_turf.contents)
		if(GLOB.typecache_elevated_structures[content.type])
			remove_timer()
			return

	// If all other checks fail, then begin decomposition.
	timerid = addtimer(CALLBACK(src, .proc/decompose), time_remaining, TIMER_STOPPABLE | TIMER_UNIQUE)

/datum/component/decomposition/Destroy()
	remove_timer()
	return ..()

/datum/component/decomposition/proc/remove_timer()
	if(active_timers) // Makes sure there's an active timer to delete.
		time_remaining = timeleft(timerid)
		deltimer(timerid)

/datum/component/decomposition/proc/dropped()
	SIGNAL_HANDLER
	protected = FALSE
	handle_movement()

/datum/component/decomposition/proc/picked_up()
	SIGNAL_HANDLER
	remove_timer()
	protected = TRUE
	if(!handled)
		handled = TRUE

/datum/component/decomposition/proc/decompose()
	var/obj/decomp = parent //Lets us spawn things at decomp
	if(produce_ants)
		new /obj/effect/decal/cleanable/ants(decomp.loc)
	new decomp_result(decomp.loc)
	decomp.visible_message("[decomp] gets overtaken by mold and ants! Gross!")
	qdel(decomp)
	return

/datum/component/decomposition/proc/examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER
	var/time_d = 0
	if(active_timers) // Is the timer currently applied to this?
		time_d = timeleft(timerid)
	else
		time_d = time_remaining
	switch(examine_type)
		if(DECOMP_EXAM_NORMAL)// All other types
			switch(time_d) // Deciseconds used so there's no gaps between examine times.
				if(3001 to 4500) // 7.5 to 5 Minutes left
					examine_list += "[parent] looks kinda stale."
				if(1501 to 3000) // 5 to 2.5 Minutes left
					examine_list += "[parent] is starting to look pretty gross."
				if(1 to 1500) // 2.5 Minutes to 1 Decisecond left
					examine_list += "[parent] looks barely edible."
		if(DECOMP_EXAM_GROSS) // Gross food
			switch(time_d)
				if(2101 to 3150) // 5.25 to 3.5 Minutes
					examine_list += "[parent] looks kinda stale."
				if(1050 to 2100) // 3.5 to 1.75 Minutes left
					examine_list += "[parent] is starting to look pretty gross."
				if(1 to 1051) // 1.75 Minutes to 1 Decisecond left
					examine_list += "[parent] looks barely edible."
		if(DECOMP_EXAM_RAW) // Raw food
			switch(time_d)
				if(1501 to 2250) // 3.75 to 2.5 Minutes left
					examine_list += "[parent] looks kinda stale."
				if(751 to 1500) // 2.5 to 1.25 Minutes left
					examine_list += "[parent] is starting to look pretty gross."
				if(1 to 750) // 1.25 Minutes to 1 Decisecond left
					examine_list += "[parent] looks barely edible."

#undef DECOMPOSITION_TIME
#undef DECOMPOSITION_TIME_GROSS
#undef DECOMPOSITION_TIME_RAW

#undef DECOMP_EXAM_NORMAL
#undef DECOMP_EXAM_GROSS
#undef DECOMP_EXAM_RAW


code:
/*!

This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items).
These items take a specific time to eat, and can do most of the things our original food items could.

Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else:
	Components:
	Drying component (jerky etc)
	Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.)

	Misc:
	Something for cakes (You can store things inside)

*/
/datum/component/edible
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
	///Amount of reagents taken per bite
	var/bite_consumption = 2
	///Amount of bites taken so far
	var/bitecount = 0
	///Flags for food
	var/food_flags = NONE
	///Bitfield of the types of this food
	var/foodtypes = NONE
	///Amount of seconds it takes to eat this food
	var/eat_time = 30
	///Defines how much it lowers someones satiety (Need to eat, essentialy)
	var/junkiness = 0
	///Message to send when eating
	var/list/eatverbs
	///Callback to be ran for when you take a bite of something
	var/datum/callback/after_eat
	///Callback to be ran for when you take a bite of something
	var/datum/callback/on_consume
	///Callback to be ran for when the code check if the food is liked, allowing for unique overrides for special foods like donuts with cops.
	var/datum/callback/check_liked
	///Last time we checked for food likes
	var/last_check_time
	///The initial reagents of this food when it is made
	var/list/initial_reagents
	///The initial volume of the foods reagents
	var/volume
	///The flavortext for taste (haha get it flavor text)
	var/list/tastes
	///The type of atom this creates when the object is microwaved.
	var/atom/microwaved_type

/datum/component/edible/Initialize(
	list/initial_reagents,
	food_flags = NONE,
	foodtypes = NONE,
	volume = 50,
	eat_time = 10,
	list/tastes,
	list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"),
	bite_consumption = 2,
	microwaved_type,
	junkiness,
	datum/callback/after_eat,
	datum/callback/on_consume,
	datum/callback/check_liked,
)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
	RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, .proc/UseByAnimal)
	RegisterSignal(parent, COMSIG_ATOM_CHECKPARTS, .proc/OnCraft)
	RegisterSignal(parent, COMSIG_ATOM_CREATEDBY_PROCESSING, .proc/OnProcessed)
	RegisterSignal(parent, COMSIG_ITEM_MICROWAVE_COOKED, .proc/OnMicrowaveCooked)
	RegisterSignal(parent, COMSIG_EDIBLE_INGREDIENT_ADDED, .proc/edible_ingredient_added)
	RegisterSignal(parent, COMSIG_OOZE_EAT_ATOM, .proc/on_ooze_eat)

	var/static/list/loc_connections = list(
		COMSIG_ATOM_ENTERED = .proc/on_entered,
	)
	AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)

	if(isitem(parent))
		RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/UseFromHand)
		RegisterSignal(parent, COMSIG_ITEM_FRIED, .proc/OnFried)
		RegisterSignal(parent, COMSIG_ITEM_MICROWAVE_ACT, .proc/OnMicrowaved)
		RegisterSignal(parent, COMSIG_ITEM_USED_AS_INGREDIENT, .proc/used_to_customize)

		var/obj/item/item = parent
		if (!item.grind_results)
			item.grind_results = list() //If this doesn't already exist, add it as an empty list. This is needed for the grinder to accept it.

	else if(isturf(parent) || isstructure(parent))
		RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/TryToEatIt)

	src.bite_consumption = bite_consumption
	src.food_flags = food_flags
	src.foodtypes = foodtypes
	src.eat_time = eat_time
	src.eatverbs = string_list(eatverbs)
	src.junkiness = junkiness
	src.after_eat = after_eat
	src.on_consume = on_consume
	src.initial_reagents = string_assoc_list(initial_reagents)
	src.tastes = string_assoc_list(tastes)
	src.microwaved_type = microwaved_type
	src.check_liked = check_liked

	var/atom/owner = parent

	owner.create_reagents(volume, INJECTABLE)

	for(var/rid in initial_reagents)
		var/amount = initial_reagents[rid]
		if(length(tastes) && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
			owner.reagents.add_reagent(rid, amount, tastes.Copy())
		else
			owner.reagents.add_reagent(rid, amount)

/datum/component/edible/InheritComponent(
	datum/component/C,
	i_am_original,
	list/initial_reagents,
	food_flags = NONE,
	foodtypes = NONE,
	volume = 50,
	eat_time = 10,
	list/tastes,
	list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"),
	bite_consumption = 2,
	microwaved_type,
	junkiness,
	datum/callback/after_eat,
	datum/callback/on_consume,
	datum/callback/check_liked,
)
	. = ..()
	src.bite_consumption = bite_consumption
	src.food_flags = food_flags
	src.foodtypes = foodtypes
	src.eat_time = eat_time
	src.eatverbs = eatverbs
	src.junkiness = junkiness
	src.after_eat = after_eat
	src.on_consume = on_consume

/datum/component/edible/Destroy(force, silent)
	QDEL_NULL(after_eat)
	QDEL_NULL(on_consume)
	return ..()

/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER

	if(microwaved_type)
		examine_list += "[parent] could be <b>microwaved</b> into [initial(microwaved_type.name)]!"

	if(!(food_flags & FOOD_IN_CONTAINER))
		switch (bitecount)
			if (0)
				return
			if(1)
				examine_list += "[parent] was bitten by someone!"
			if(2,3)
				examine_list += "[parent] was bitten [bitecount] times!"
			else
				examine_list += "[parent] was bitten multiple times!"

/datum/component/edible/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user)
	SIGNAL_HANDLER

	return TryToEat(M, user)

/datum/component/edible/proc/TryToEatIt(datum/source, mob/user)
	SIGNAL_HANDLER

	return TryToEat(user, user)

/datum/component/edible/proc/OnFried(fry_object)
	SIGNAL_HANDLER
	var/atom/our_atom = parent
	our_atom.reagents.trans_to(fry_object, our_atom.reagents.total_volume)
	qdel(our_atom)
	return COMSIG_FRYING_HANDLED

///Called when food is created through processing (Usually this means it was sliced). We use this to pass the OG items reagents.
/datum/component/edible/proc/OnProcessed(datum/source, atom/original_atom, list/chosen_processing_option)
	SIGNAL_HANDLER

	if(!original_atom.reagents)
		return

	var/atom/this_food = parent
	var/reagents_for_slice = chosen_processing_option[TOOL_PROCESSING_AMOUNT]

	this_food.create_reagents(volume) //Make sure we have a reagent container

	original_atom.reagents.trans_to(this_food, reagents_for_slice)

	if(original_atom.name != initial(original_atom.name))
		this_food.name = "slice of [original_atom.name]"
	if(original_atom.desc != initial(original_atom.desc))
		this_food.desc = "[original_atom.desc]"

///Called when food is crafted through a crafting recipe datum.
/datum/component/edible/proc/OnCraft(datum/source, list/parts_list, datum/crafting_recipe/food/recipe)
	SIGNAL_HANDLER

	var/atom/this_food = parent

	this_food.reagents.multiply_reagents(CRAFTED_FOOD_BASE_REAGENT_MODIFIER)

	for(var/obj/item/food/crafted_part in parts_list)
		crafted_part.reagents?.trans_to(this_food.reagents, crafted_part.reagents.maximum_volume, CRAFTED_FOOD_INGREDIENT_REAGENT_MODIFIER)

	SSblackbox.record_feedback("tally", "food_made", 1, type)

/datum/component/edible/proc/OnMicrowaved(datum/source, obj/machinery/microwave/used_microwave)
	SIGNAL_HANDLER

	var/turf/parent_turf = get_turf(parent)

	if(!microwaved_type)
		new /obj/item/food/badrecipe(parent_turf)
		qdel(parent)
		return

	var/obj/item/result

	result = new microwaved_type(parent_turf)

	var/efficiency = istype(used_microwave) ? used_microwave.efficiency : 1

	SEND_SIGNAL(result, COMSIG_ITEM_MICROWAVE_COOKED, parent, efficiency)

	SSblackbox.record_feedback("tally", "food_made", 1, result.type)
	qdel(parent)
	return COMPONENT_SUCCESFUL_MICROWAVE

///Corrects the reagents on the newly cooked food
/datum/component/edible/proc/OnMicrowaveCooked(datum/source, obj/item/source_item, cooking_efficiency = 1)
	SIGNAL_HANDLER

	var/atom/this_food = parent

	this_food.reagents.multiply_reagents(cooking_efficiency * CRAFTED_FOOD_BASE_REAGENT_MODIFIER)

	source_item.reagents?.trans_to(this_food, source_item.reagents.total_volume)

///Makes sure the thing hasn't been destroyed or fully eaten to prevent eating phantom edibles
/datum/component/edible/proc/IsFoodGone(atom/owner, mob/living/feeder)
	if(QDELETED(owner)|| !(IS_EDIBLE(owner)))
		return TRUE
	if(owner.reagents.total_volume)
		return FALSE
	return TRUE

///All the checks for the act of eating itself and
/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder)

	set waitfor = FALSE

	var/atom/owner = parent
	if(feeder.combat_mode)
		return

	. = COMPONENT_CANCEL_ATTACK_CHAIN //Point of no return I suppose

	if(IsFoodGone(owner, feeder))
		return

	if(!CanConsume(eater, feeder))
		return
	var/fullness = eater.get_fullness() + 10 //The theoretical fullness of the person eating if they were to eat this

	if(eater == feeder)//If you're eating it yourself.
		if(eat_time && !do_mob(feeder, eater, eat_time, timed_action_flags = food_flags & FOOD_FINGER_FOOD ? IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE : NONE)) //Gotta pass the minimal eat time
			return
		if(IsFoodGone(owner, feeder))
			return
		var/eatverb = pick(eatverbs)

		var/message_to_nearby_audience = ""
		var/message_to_consumer = ""
		var/message_to_blind_consumer = ""

		if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS))
			to_chat(eater, span_warning("You don't feel like eating any more junk food at the moment!"))
			return
		else if(fullness > (600 * (1 + eater.overeatduration / (4000 SECONDS)))) // The more you eat - the more you can eat
			message_to_nearby_audience = span_warning("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!")
			message_to_consumer = span_warning("You cannot force any more of \the [parent] to go down your throat!")
			message_to_blind_consumer = message_to_consumer
			eater.show_message(message_to_consumer, MSG_VISUAL, message_to_blind_consumer)
			eater.visible_message(message_to_nearby_audience, ignored_mobs = eater)
			//if we're too full, return because we can't eat whatever it is we're trying to eat
			return
		else if(fullness > 500)
			message_to_nearby_audience = span_notice("[eater] unwillingly [eatverb]s a bit of \the [parent].")
			message_to_consumer = span_notice("You unwillingly [eatverb] a bit of \the [parent].")
		else if(fullness > 150)
			message_to_nearby_audience = span_notice("[eater] [eatverb]s \the [parent].")
			message_to_consumer = span_notice("You [eatverb] \the [parent].")
		else if(fullness > 50)
			message_to_nearby_audience = span_notice("[eater] hungrily [eatverb]s \the [parent].")
			message_to_consumer = span_notice("You hungrily [eatverb] \the [parent].")
		else
			message_to_nearby_audience = span_notice("[eater] hungrily [eatverb]s \the [parent], gobbling it down!")
			message_to_consumer = span_notice("You hungrily [eatverb] \the [parent], gobbling it down!")

		//if we're blind, we want to feel how hungrily we ate that food
		message_to_blind_consumer = message_to_consumer
		eater.show_message(message_to_consumer, MSG_VISUAL, message_to_blind_consumer)
		eater.visible_message(message_to_nearby_audience, ignored_mobs = eater)

	else //If you're feeding it to someone else.
		if(isbrain(eater))
			to_chat(feeder, span_warning("[eater] doesn't seem to have a mouth!"))
			return
		if(fullness <= (600 * (1 + eater.overeatduration / (2000 SECONDS))))
			eater.visible_message(span_danger("[feeder] attempts to feed [eater] [parent]."), \
									span_userdanger("[feeder] attempts to feed you [parent]."))
		else
			eater.visible_message(span_warning("[feeder] cannot force any more of [parent] down [eater]'s throat!"), \
									span_warning("[feeder] cannot force any more of [parent] down your throat!"))
			return
		if(!do_mob(feeder, eater)) //Wait 3 seconds before you can feed
			return
		if(IsFoodGone(owner, feeder))
			return
		log_combat(feeder, eater, "fed", owner.reagents.log_list())
		eater.visible_message(span_danger("[feeder] forces [eater] to eat [parent]!"), \
									span_userdanger("[feeder] forces you to eat [parent]!"))

	TakeBite(eater, feeder)

	//If we're not force-feeding and there's an eat delay, try take another bite
	if(eater == feeder && eat_time)
		INVOKE_ASYNC(src, .proc/TryToEat, eater, feeder)

///This function lets the eater take a bite and transfers the reagents to the eater.
/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder)

	var/atom/owner = parent

	if(!owner?.reagents)
		stack_trace("[eater] failed to bite [owner], because [owner] had no reagents.")
		return FALSE
	if(eater.satiety > -200)
		eater.satiety -= junkiness
	playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
	if(owner.reagents.total_volume)
		SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder, bitecount, bite_consumption)
		var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
		owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, methods = INGEST)
		bitecount++
		if(!owner.reagents.total_volume)
			On_Consume(eater, feeder)
		checkLiked(fraction, eater)

		//Invoke our after eat callback if it is valid
		if(after_eat)
			after_eat.Invoke(eater, feeder, bitecount)

		return TRUE

///Checks whether or not the eater can actually consume the food
/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder)
	if(!iscarbon(eater))
		return FALSE
	var/mob/living/carbon/C = eater
	var/covered = ""
	if(C.is_mouth_covered(head_only = 1))
		covered = "headgear"
	else if(C.is_mouth_covered(mask_only = 1))
		covered = "mask"
	if(covered)
		var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]"
		to_chat(feeder, span_warning("You have to remove [who] [covered] first!"))
		return FALSE
	return TRUE

///Check foodtypes to see if we should send a moodlet
/datum/component/edible/proc/checkLiked(fraction, mob/M)
	if(last_check_time + 50 > world.time)
		return FALSE
	if(!ishuman(M))
		return FALSE
	var/mob/living/carbon/human/H = M

	//Bruh this breakfast thing is cringe and shouldve been handled separately from food-types, remove this in the future (Actually, just kill foodtypes in general)
	if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
		SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
	last_check_time = world.time

	if(HAS_TRAIT(H, TRAIT_AGEUSIA))
		if(foodtypes & H.dna.species.toxic_food)
			to_chat(H, span_warning("You don't feel so good..."))
			H.adjust_disgust(25 + 30 * fraction)
		return // Don't care about the later checks if user has ageusia

	var/food_taste_reaction

	if(check_liked) //Callback handling; use this as an override for special food like donuts
		food_taste_reaction = check_liked.Invoke(fraction, H)

	if(!food_taste_reaction)
		if(foodtypes & H.dna.species.toxic_food)
			food_taste_reaction = FOOD_TOXIC
		else if(foodtypes & H.dna.species.disliked_food)
			food_taste_reaction = FOOD_DISLIKED
		else if(foodtypes & H.dna.species.liked_food)
			food_taste_reaction = FOOD_LIKED

	switch(food_taste_reaction)
		if(FOOD_TOXIC)
			to_chat(H,span_warning("What the hell was that thing?!"))
			H.adjust_disgust(25 + 30 * fraction)
			SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food)
		if(FOOD_DISLIKED)
			to_chat(H,span_notice("That didn't taste very good..."))
			H.adjust_disgust(11 + 15 * fraction)
			SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food)
		if(FOOD_LIKED)
			to_chat(H,span_notice("I love this taste!"))
			H.adjust_disgust(-5 + -2.5 * fraction)
			SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food)
			if(istype(parent, /obj/item/food))
				var/obj/item/food/memorable_food = parent
				if(memorable_food.venue_value >= FOOD_PRICE_EXOTIC)
					H.mind?.add_memory(MEMORY_MEAL, list(DETAIL_FOOD = memorable_food), story_value = STORY_VALUE_OKAY)

///Delete the item when it is fully eaten
/datum/component/edible/proc/On_Consume(mob/living/eater, mob/living/feeder)
	SEND_SIGNAL(parent, COMSIG_FOOD_CONSUMED, eater, feeder)

	on_consume?.Invoke(eater, feeder)

	to_chat(feeder, span_warning("There is nothing left of [parent], oh no!"))
	if(isturf(parent))
		var/turf/T = parent
		T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
	else
		qdel(parent)

///Ability to feed food to puppers
/datum/component/edible/proc/UseByAnimal(datum/source, mob/user)

	SIGNAL_HANDLER

	var/atom/owner = parent

	if(!isdog(user))
		return
	var/mob/living/L = user
	if(bitecount == 0 || prob(50))
		L.manual_emote("nibbles away at \the [parent].")
	bitecount++
	. = COMPONENT_CANCEL_ATTACK_CHAIN
	L.taste(owner.reagents) // why should carbons get all the fun?
	if(bitecount >= 5)
		var/satisfaction_text = pick("burps from enjoyment.", "yaps for more!", "woofs twice.", "looks at the area where \the [parent] was.")
		L.manual_emote(satisfaction_text)
		qdel(parent)

///Ability to feed food to puppers
/datum/component/edible/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
	SIGNAL_HANDLER
	SEND_SIGNAL(parent, COMSIG_FOOD_CROSSED, arrived, bitecount)

///Response to being used to customize something
/datum/component/edible/proc/used_to_customize(datum/source, atom/customized)
	SIGNAL_HANDLER

	SEND_SIGNAL(customized, COMSIG_EDIBLE_INGREDIENT_ADDED, src)

///Response to an edible ingredient being added to parent.
/datum/component/edible/proc/edible_ingredient_added(datum/source, datum/component/edible/ingredient)
	SIGNAL_HANDLER

	var/datum/component/edible/E = ingredient
	if (LAZYLEN(E.tastes))
		tastes = tastes.Copy()
		for (var/t in E.tastes)
			tastes[t] += E.tastes[t]
	foodtypes |= E.foodtypes

/// Response to oozes trying to eat something edible
/datum/component/edible/proc/on_ooze_eat(datum/source, mob/eater, edible_flags)
	SIGNAL_HANDLER

	if(foodtypes & edible_flags)
		var/atom/eaten_food = parent
		eaten_food.reagents.trans_to(eater, eaten_food.reagents.total_volume, transfered_by = eater)
		eater.visible_message(span_warning("[src] eats [eaten_food]!"), span_notice("You eat [eaten_food]."))
		playsound(get_turf(eater),'sound/items/eatfood.ogg', rand(30,50), TRUE)
		qdel(eaten_food)
		return COMPONENT_ATOM_EATEN


code:
#define SCOOP_OFFSET 4
#define SWEETENER_PER_SCOOP 10
#define EXTRA_MAX_VOLUME_PER_SCOOP 20

/// Ice Cream Holder: Allows the edible parent object to be used as an ice cream cone (or cup... in a next future).
/datum/component/ice_cream_holder
	/// List of servings of ice cream it is holding at the moment.
	var/list/scoops
	/// Servings of ice cream with custom names as key, and their base ones as assoc. (useful for mob/custom ice cream)
	var/list/special_scoops
	/*
	 * List of scoop overlays to add on update_overlays(). Separated from list/scoops considering how byond is inconsistent
	 * at handling duplicate keys and assocs.
	 */
	var/list/scoop_overlays
	/// Number of servings of ice cream it can get through normal methods.
	var/max_scoops = DEFAULT_MAX_ICE_CREAM_SCOOPS
	/// Changes the name of the food depending on amount and flavours of ice cream on it.
	var/change_name = TRUE
	/// name to use, if set, in place of [src] on update_name.
	var/filled_name
	/*
	 * Ditto as change_name, but for descriptions.
	 * If false, an examine signal is registered to let the amount and types of held ice cream be known anyway.
	 */
	var/change_desc = FALSE
	/// pixel offsets for scoop overlays. Useful for objects with off-centered sprites.
	var/x_offset = 0
	var/y_offset = 0
	/*
	 * Extra reagent generated each time a new serving is added. Because apparently our sundaes are instead banana splits!
	 * And banana juice plus sugar equals laughter! (I'll leave it unchanged for honkdaes though)
	 */
	var/datum/reagent/sweetener

/datum/component/ice_cream_holder/Initialize(max_scoops = DEFAULT_MAX_ICE_CREAM_SCOOPS,
											change_name = TRUE,
											filled_name,
											change_desc = FALSE,
											x_offset = 0,
											y_offset = 0,
											datum/reagent/sweetener = /datum/reagent/consumable/sugar,
											list/prefill_flavours)
	if(!IS_EDIBLE(parent)) /// There is no easy way to add servings to those non-item edibles, but I won't stop you.
		return COMPONENT_INCOMPATIBLE

	var/atom/owner = parent

	src.max_scoops = max_scoops
	src.change_name = change_name
	src.filled_name = filled_name
	src.change_desc = change_desc
	src.x_offset = x_offset
	src.y_offset = y_offset
	src.sweetener = sweetener

	RegisterSignal(owner, COMSIG_ITEM_ATTACK_OBJ, .proc/on_item_attack_obj)
	RegisterSignal(owner, COMSIG_ATOM_UPDATE_OVERLAYS, .proc/on_update_overlays)
	if(change_name)
		RegisterSignal(owner, COMSIG_ATOM_UPDATE_NAME, .proc/on_update_name)
	if(!change_desc)
		RegisterSignal(owner, COMSIG_PARENT_EXAMINE_MORE, .proc/on_examine_more)
	else
		RegisterSignal(owner, COMSIG_ATOM_UPDATE_DESC, .proc/on_update_desc)

	if(prefill_flavours)
		for(var/entry in prefill_flavours)
			var/list/flavour_args = list(src) + prefill_flavours[entry]
			var/datum/ice_cream_flavour/flavour = GLOB.ice_cream_flavours[entry]
			flavour?.add_flavour(arglist(flavour_args))

/datum/component/ice_cream_holder/proc/on_update_name(atom/source, updates)
	SIGNAL_HANDLER
	var/obj/obj = source
	if(istype(obj) && obj.renamedByPlayer) //Renamed by the player.
		return
	var/scoops_len = length(scoops)
	if(!scoops_len)
		source.name = initial(source.name)
	else
		var/name_to_use = filled_name || initial(source.name)
		var/list/unique_list = unique_list(scoops)
		if(scoops_len > 1 && length(unique_list) == 1) // multiple flavours, and all of the same type
			source.name = "[make_tuple(scoops_len)] [scoops[1]] [name_to_use]" // "double vanilla" sounds cooler than just "vanilla"
		else
			source.name = "[english_list(unique_list)] [name_to_use]"

/datum/component/ice_cream_holder/proc/on_update_desc(atom/source, updates)
	SIGNAL_HANDLER
	var/obj/obj = source
	if(istype(obj) && obj.renamedByPlayer) //Renamed by the player.
		return
	var/scoops_len = length(scoops)
	if(!scoops_len)
		source.desc = initial(source.desc)
	else if(scoops_len == 1 || length(unique_list(scoops)) == 1) /// Only one flavour.
		var/key = scoops[1]
		var/datum/ice_cream_flavour/flavour = GLOB.ice_cream_flavours[LAZYACCESS(special_scoops, key) || key]
		if(!flavour?.desc) //I scream.
			source.desc = initial(source.desc)
		else
			source.desc = replacetext(replacetext("[flavour.desc_prefix] [flavour.desc]", "$CONE_NAME", initial(source.name)), "$CUSTOM_NAME", key)
	else /// Many flavours.
		source.desc = "A delicious [initial(source.name)] filled with scoops of [english_list(scoops)] icecream. That's as many as [scoops_len] scoops!"

/datum/component/ice_cream_holder/proc/on_examine_more(atom/source, mob/mob, list/examine_list)
	SIGNAL_HANDLER
	var/scoops_len = length(scoops)
	if(scoops_len == 1 || length(unique_list(scoops)) == 1) /// Only one flavour.
		var/key = scoops[1]
		var/datum/ice_cream_flavour/flavour = GLOB.ice_cream_flavours[LAZYACCESS(special_scoops, key) || key]
		if(flavour?.desc) //I scream.
			examine_list += "[source.p_theyre(TRUE)] filled with scoops of [flavour ? flavour.name : "broken, unhappy"] icecream."
		else
			examine_list += replacetext(replacetext("[source.p_theyre(TRUE)] [flavour.desc]", "$CONE_NAME", initial(source.name)), "$CUSTOM_NAME", key)
	else /// Many flavours.
		examine_list += "[source.p_theyre(TRUE)] filled with scoops of [english_list(scoops)] icecream. That's as many as [scoops_len] scoops!"

/datum/component/ice_cream_holder/proc/on_update_overlays(atom/source, list/new_overlays)
	SIGNAL_HANDLER
	if(!scoops)
		return
	var/added_offset = 0
	for(var/i in 1 to length(scoop_overlays))
		var/image/overlay = scoop_overlays[i]
		if(istext(overlay))
			overlay = image('icons/obj/kitchen.dmi', overlay)
		overlay.pixel_x = x_offset
		overlay.pixel_y = y_offset + added_offset
		new_overlays += overlay
		added_offset += SCOOP_OFFSET

/// Attack the ice cream vat to get some ice cream. This will change as new ways of getting ice cream are added.
/datum/component/ice_cream_holder/proc/on_item_attack_obj(obj/item/source, obj/target, mob/user)
	SIGNAL_HANDLER
	if(!istype(target, /obj/machinery/icecream_vat))
		return
	var/obj/machinery/icecream_vat/dispenser = target
	if(length(scoops) < max_scoops)
		if(dispenser.product_types[dispenser.selected_flavour] > 0)
			var/datum/ice_cream_flavour/flavour = GLOB.ice_cream_flavours[dispenser.selected_flavour]
			if(flavour.add_flavour(src, dispenser.beaker?.reagents.total_volume ? dispenser.beaker.reagents : null))
				dispenser.visible_message("[icon2html(dispenser, viewers(source))] [span_info("[user] scoops delicious [dispenser.selected_flavour] ice cream into [source].")]")
				dispenser.product_types[dispenser.selected_flavour]--
				INVOKE_ASYNC(dispenser, /obj/machinery/icecream_vat.proc/updateDialog)
		else
			to_chat(user, span_warning("There is not enough ice cream left!"))
	else
		to_chat(user, span_warning("[source] can't hold anymore ice cream!"))
	return COMPONENT_CANCEL_ATTACK_CHAIN

/////ICE CREAM FLAVOUR DATUM STUFF

GLOBAL_LIST_INIT_TYPED(ice_cream_flavours, /datum/ice_cream_flavour, init_ice_cream_flavours())

/proc/init_ice_cream_flavours()
	. = list()
	for(var/datum/ice_cream_flavour/flavour as anything in subtypesof(/datum/ice_cream_flavour))
		flavour = new flavour
		.[flavour.name] = flavour

/*
 * The ice cream flavour datum. What makes these digital, frozen snacks so yummy.
 * They are singletons, so please bear with me if they feel a little tortous to use at time.
 */
/datum/ice_cream_flavour
	/// Make sure the same name is not found on other types; These are singletons keyed by their name after all.
	var/name = "Coderlicious Gourmet Double Deluxe Undefined"
	/// The icon state of the flavour, overlay or not.
	var/icon_state = "icecream_vanilla"
	/*
	 * The fluff text sent to the examiner when the snack has only one flavour of ice cream.
	 * $CONE_NAME and $CUSTOM_NAME are both placeholders for the cone and the custom ice cream name respectively.
	 */
	var/desc = ""
	/*
	 * Depending on the value of the [/datum/component/ice_cream/var/change_desc] bool, 'desc' may effectively be the description
	 * or a text string shown on [/atom/proc/examine_more]. In the former case, the desc is joined with this prefix.
	 */
	var/desc_prefix = "A delicious $CONE_NAME"
	/// The ingredients required to produce a unit with the ice cream vat, these are multiplied by 3.
	var/list/ingredients = list(/datum/reagent/consumable/milk, /datum/reagent/consumable/ice, /datum/reagent/consumable/vanilla)
	/// The same as above, but in a readable text generated on New() that can also contain fluff ingredients such as "lot of love" or "optional flavorings".
	var/ingredients_text = ""
	/// the reagent added in 'add_flavour()'
	var/reagent_type
	/// the amount of reagent added in 'add_flavour()'
	var/reagent_amount = 3
	/// Is this flavour shown in the ice cream vat menu or not?
	var/hidden = FALSE

/datum/ice_cream_flavour/New()
	if(ingredients)
		ingredients_text = "(Ingredients: [reagent_paths_list_to_text(ingredients, ingredients_text)])"

/// Adds a new flavour to the ice cream cone.
/datum/ice_cream_flavour/proc/add_flavour(datum/component/ice_cream_holder/target, datum/reagents/R, custom_name)
	var/atom/owner = target.parent
	LAZYADD(target.scoops, custom_name || name)
	if(icon_state)
		LAZYADD(target.scoop_overlays, icon_state)
	if(custom_name)
		LAZYSET(target.special_scoops, custom_name, name)

	owner.reagents.maximum_volume += EXTRA_MAX_VOLUME_PER_SCOOP
	if(reagent_type)
		owner.reagents.add_reagent(reagent_type, reagent_amount, reagtemp = T0C)
	// Add some sugar/sweetener to make it a more substantial snack.
	owner.reagents.add_reagent(target.sweetener, SWEETENER_PER_SCOOP, reagtemp = T0C)

	var/update_flags = UPDATE_ICON
	if(target.change_name)
		update_flags |= UPDATE_NAME
	if(target.change_desc)
		update_flags |= UPDATE_DESC
	owner.update_appearance(update_flags)
	return TRUE

///// OUR TYPES OF ICE CREAM, COME GET SOME.

/datum/ice_cream_flavour/vanilla
	name = ICE_CREAM_VANILLA
	desc = "filled with vanilla ice cream. All the other ice creams take content from it."
	reagent_type = /datum/reagent/consumable/vanilla

/datum/ice_cream_flavour/chocolate
	name = ICE_CREAM_CHOCOLATE
	icon_state = "icecream_chocolate"
	desc = "filled with chocolate ice cream. Surprisingly, made with real cocoa."
	ingredients = list(/datum/reagent/consumable/milk, /datum/reagent/consumable/ice, /datum/reagent/consumable/coco)
	reagent_type = /datum/reagent/consumable/coco

/datum/ice_cream_flavour/strawberry
	name = ICE_CREAM_STRAWBERRY
	icon_state = "icecream_strawberry"
	desc = "filled with strawberry ice cream. Definitely not made with real strawberries."
	ingredients = list(/datum/reagent/consumable/milk, /datum/reagent/consumable/ice, /datum/reagent/consumable/berryjuice)
	reagent_type = /datum/reagent/consumable/berryjuice

/datum/ice_cream_flavour/blue
	name = ICE_CREAM_BLUE
	icon_state = "icecream_blue"
	desc = "filled with blue ice cream. Made with real... blue?"
	ingredients = list(/datum/reagent/consumable/milk, /datum/reagent/consumable/ice, /datum/reagent/consumable/ethanol/singulo)
	reagent_type = /datum/reagent/consumable/ethanol/singulo

/datum/ice_cream_flavour/mob
	name = ICE_CREAM_MOB
	icon_state = "icecream_mob"
	desc = "filled with bright red ice cream. That's probably not strawberry..."
	desc_prefix = "A suspicious $CONE_NAME"
	reagent_type = /datum/reagent/liquidgibs
	hidden = TRUE

/datum/ice_cream_flavour/custom
	name = ICE_CREAM_CUSTOM
	icon_state = "" //has its own mutable appearance overlay.
	desc = "filled with artisanal icecream. Made with real $CUSTOM_NAME. Ain't that something."
	ingredients = list(/datum/reagent/consumable/milk, /datum/reagent/consumable/ice)
	ingredients_text = "optional flavorings"

/datum/ice_cream_flavour/custom/add_flavour(datum/component/ice_cream_holder/target, datum/reagents/R, custom_name)
	if(!R || R.total_volume < 4) //consumable reagents have stronger taste so higher volume are required to allow non-food flavourings to break through better.
		return GLOB.ice_cream_flavours[ICE_CREAM_BLAND].add_flavour(target) //Bland, sugary ice and milk.
	var/image/flavoring = image('icons/obj/kitchen.dmi', "icecream_custom")
	var/datum/reagent/master = R.get_master_reagent()
	custom_name = lowertext(master.name) // reagent names are capitalized, while items' aren't.
	flavoring.color = master.color
	LAZYADD(target.scoop_overlays, flavoring)
	. = ..() // Make some space for reagents before attempting to transfer some to the target.
	R.trans_to(target.parent, 4)

/datum/ice_cream_flavour/bland
	name = ICE_CREAM_BLAND
	icon_state = "icecream_custom"
	desc = "filled with anemic, flavorless icecream. You wonder why this was ever scooped..."
	hidden = TRUE

#undef SCOOP_OFFSET
#undef SWEETENER_PER_SCOOP
#undef EXTRA_MAX_VOLUME_PER_SCOOP


code:
/datum/component/forensics
	dupe_mode = COMPONENT_DUPE_UNIQUE
	can_transfer = TRUE
	var/list/fingerprints //assoc print = print
	var/list/hiddenprints //assoc ckey = realname/gloves/ckey
	var/list/blood_DNA //assoc dna = bloodtype
	var/list/fibers //assoc print = print

/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL.
	fingerprints = LAZY_LISTS_OR(fingerprints, F.fingerprints)
	hiddenprints = LAZY_LISTS_OR(hiddenprints, F.hiddenprints)
	blood_DNA = LAZY_LISTS_OR(blood_DNA, F.blood_DNA)
	fibers = LAZY_LISTS_OR(fibers, F.fibers)
	check_blood()
	return ..()

/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	fingerprints = new_fingerprints
	hiddenprints = new_hiddenprints
	blood_DNA = new_blood_DNA
	fibers = new_fibers
	check_blood()

/datum/component/forensics/RegisterWithParent()
	check_blood()
	RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act)

/datum/component/forensics/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT))

/datum/component/forensics/PostTransfer()
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE

/datum/component/forensics/proc/wipe_fingerprints()
	fingerprints = null
	return TRUE

/datum/component/forensics/proc/wipe_hiddenprints()
	return //no.

/datum/component/forensics/proc/wipe_blood_DNA()
	blood_DNA = null
	return TRUE

/datum/component/forensics/proc/wipe_fibers()
	fibers = null
	return TRUE

/datum/component/forensics/proc/clean_act(datum/source, clean_types)
	SIGNAL_HANDLER

	. = NONE
	if(clean_types & CLEAN_TYPE_FINGERPRINTS)
		wipe_fingerprints()
		. = COMPONENT_CLEANED
	if(clean_types & CLEAN_TYPE_BLOOD)
		wipe_blood_DNA()
		. = COMPONENT_CLEANED
	if(clean_types & CLEAN_TYPE_FIBERS)
		wipe_fibers()
		. = COMPONENT_CLEANED

/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text)
	if(!length(_fingerprints))
		return
	LAZYINITLIST(fingerprints)
	for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
		fingerprints[i] = i
	return TRUE

/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE)
	if(!isliving(M))
		if(!iscameramob(M))
			return
		if(isaicamera(M))
			var/mob/camera/ai_eye/ai_camera = M
			if(!ai_camera.ai)
				return
			M = ai_camera.ai
	add_hiddenprint(M)
	if(ishuman(M))
		var/mob/living/carbon/human/H = M
		add_fibers(H)
		if(H.gloves) //Check if the gloves (if any) hide fingerprints
			var/obj/item/clothing/gloves/G = H.gloves
			if(G.transfer_prints)
				ignoregloves = TRUE
			if(!ignoregloves)
				H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop.
				return
		var/full_print = md5(H.dna.unique_identity)
		LAZYSET(fingerprints, full_print, full_print)
	return TRUE

/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text)
	if(!length(_fibertext))
		return
	LAZYINITLIST(fibers)
	for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours.
		fibers[i] = i
	return TRUE

/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M)
	var/fibertext
	var/item_multiplier = isitem(src)?1.2:1
	if(M.wear_suit)
		fibertext = "Material from \a [M.wear_suit]."
		if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext))
			LAZYSET(fibers, fibertext, fibertext)
		if(!(M.wear_suit.body_parts_covered & CHEST))
			if(M.w_uniform)
				fibertext = "Fibers from \a [M.w_uniform]."
				if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed.
					LAZYSET(fibers, fibertext, fibertext)
		if(!(M.wear_suit.body_parts_covered & HANDS))
			if(M.gloves)
				fibertext = "Material from a pair of [M.gloves.name]."
				if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
					LAZYSET(fibers, fibertext, fibertext)
	else if(M.w_uniform)
		fibertext = "Fibers from \a [M.w_uniform]."
		if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext))
			// "Added fibertext: [fibertext]"
			LAZYSET(fibers, fibertext, fibertext)
		if(M.gloves)
			fibertext = "Material from a pair of [M.gloves.name]."
			if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
				LAZYSET(fibers, fibertext, fibertext)
	else if(M.gloves)
		fibertext = "Material from a pair of [M.gloves.name]."
		if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
			LAZYSET(fibers, fibertext, fibertext)
	return TRUE

/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text)
	if(!length(_hiddenprints))
		return
	LAZYINITLIST(hiddenprints)
	for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
		hiddenprints[i] = _hiddenprints[i]
	return TRUE

/datum/component/forensics/proc/add_hiddenprint(mob/M)
	if(!isliving(M))
		if(!iscameramob(M))
			return
		if(isaicamera(M))
			var/mob/camera/ai_eye/ai_camera = M
			if(!ai_camera.ai)
				return
			M = ai_camera.ai
	if(!M.key)
		return
	var/hasgloves = ""
	if(ishuman(M))
		var/mob/living/carbon/human/H = M
		if(H.gloves)
			hasgloves = "(gloves)"
	var/current_time = time_stamp()
	if(!LAZYACCESS(hiddenprints, M.key))
		LAZYSET(hiddenprints, M.key, "First: \[[current_time]\] \"[M.real_name]\"[hasgloves]. Ckey: [M.ckey]")
	else
		var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), "\nLast: ")
		if(laststamppos)
			LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos))
		hiddenprints[M.key] += "\nLast: \[[current_time]\] \"[M.real_name]\"[hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else
	var/atom/A = parent
	A.fingerprintslast = M.ckey
	return TRUE

/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type)
	if(!length(dna))
		return
	LAZYINITLIST(blood_DNA)
	for(var/i in dna)
		blood_DNA[i] = dna[i]
	check_blood()
	return TRUE

/datum/component/forensics/proc/check_blood()
	if(!isitem(parent))
		return
	if(!length(blood_DNA))
		return
	parent.AddElement(/datum/element/decal/blood)