code:
//Fluoride stare
/mob/living/handle_eye_contact(mob/living/examined_mob)
	if(!istype(examined_mob) || src == examined_mob || stat >= UNCONSCIOUS || examined_mob.stat >= UNCONSCIOUS)
		return

	if(!HAS_TRAIT(src, TRAIT_FLUORIDE_STARE))
		if(!client || !LAZYACCESS(examined_mob.client?.recent_examines, src))
			return
		if(get_dist(src, examined_mob) > EYE_CONTACT_RANGE)
			return

	// check to see if their face is blocked or, if not, a signal blocks it
	if(examined_mob.is_face_visible() && SEND_SIGNAL(src, COMSIG_MOB_EYECONTACT, examined_mob, TRUE) != COMSIG_BLOCK_EYECONTACT)
		var/msg = span_smallnotice("I make eye contact with [examined_mob].")
		if(HAS_TRAIT(src, TRAIT_FLUORIDE_STARE))
			msg = span_flashinguserdanger("I make eye contact with [examined_mob].")
		addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, src, msg), 3) // so the examine signal has time to fire and this will print after

	if(is_face_visible() && SEND_SIGNAL(examined_mob, COMSIG_MOB_EYECONTACT, src, FALSE) != COMSIG_BLOCK_EYECONTACT)
		var/msg = span_smallnotice("[src] makes eye contact with you.")
		if(HAS_TRAIT(src, TRAIT_FLUORIDE_STARE))
			msg = span_flashinguserdanger("[src] makes eye contact with you.")
		addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, examined_mob, msg), 3)

//Add FoV
/mob/living/ComponentInitialize()
	. = ..()
	if(has_field_of_vision && CONFIG_GET(flag/use_field_of_vision))
		LoadComponent(/datum/component/field_of_vision)

//Update the hud smoothly
/mob/living/changeNext_move(num)
	. = ..()
	if(hud_used?.swap_hand)
		hud_used.swap_hand.last_user_move = world.time
		hud_used.swap_hand.target_time = next_move
		START_PROCESSING(SShuds, hud_used.swap_hand)

/mob/living/ZImpactDamage(turf/T, levels)
	SEND_SIGNAL(T, COMSIG_TURF_MOB_FALL, src)
	fall_scream()
	//TODO: swimming skill
	if(T.liquids?.liquid_state >= LIQUID_STATE_WAIST)
		Knockdown(2 SECONDS)
		return
	var/difficulty = (levels**2) //exponentially harder to succeed the diceroll
	//Freerunning makes it easier
	if(HAS_TRAIT(src, TRAIT_FREERUNNING))
		difficulty -= 5
	var/diceroll = diceroll(GET_MOB_ATTRIBUTE_VALUE(src, SKILL_CLIMBING)+(GET_MOB_ATTRIBUTE_VALUE(src, STAT_DEXTERITY)*2)-difficulty, 20, 2, 20)
	switch(diceroll)
		//Lucky nigga
		if(DICE_CRIT_SUCCESS)
			visible_message(span_danger("[src] regroups as [p_they()] fall[p_they() in list("it", "they") ? "" : "s"] safely on [T]!"), \
						span_userdanger("I fall down on [T], but regroup safely!"))
		if(DICE_SUCCESS)
			visible_message(span_danger("[src] braces and cushions their fall!"), \
						span_userdanger("I cushion my fall on [T]!"))
			var/ouch = (levels * 2.5) ** 2
			var/list/oofzones = pick(list(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_L_LEG), list(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_R_LEG))
			for(var/limb in oofzones)
				apply_damage(ouch, damagetype = BRUTE, def_zone = pick(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_PRECISE_R_FOOT))
			CombatKnockdown(levels * 25, levels * 1 SECONDS)
		if(DICE_FAILURE)
			visible_message(span_danger("[src] crashes into [T]!"), \
						span_userdanger("I crash into [T]!"))
			var/ouch = (levels * 3) ** 2
			var/list/oofzones = pick(list(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_L_LEG), list(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_R_LEG))
			for(var/limb in oofzones)
				apply_damage(ouch, damagetype = BRUTE, def_zone = pick(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_PRECISE_R_FOOT))
			oofzones = pick(list(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_L_LEG), list(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_R_LEG))
			for(var/limb in oofzones)
				apply_damage(ouch, damagetype = BRUTE, def_zone = pick(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_PRECISE_R_FOOT))
			CombatKnockdown(levels * 35, levels * 2.5 SECONDS, levels * 1 SECONDS, TRUE)
		if(DICE_CRIT_FAILURE)
			visible_message(span_danger("[src] crashes into [T] with a sickening noise!"), \
							span_userdanger("I crash into [T] with a sickening noise!"))
			var/ouch = (levels * 3.5) ** 2
			var/list/oofzones = pick(list(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_L_LEG), list(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_R_LEG))
			for(var/limb in oofzones)
				apply_damage(ouch, damagetype = BRUTE, def_zone = pick(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_PRECISE_R_FOOT))
			oofzones = pick(list(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_L_LEG), list(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_R_LEG))
			for(var/limb in oofzones)
				apply_damage(ouch, damagetype = BRUTE, def_zone = pick(BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_PRECISE_R_FOOT))
			CombatKnockdown(levels * 50, levels * 4 SECONDS, levels * 2 SECONDS, TRUE)

/mob/living/get_temperature(datum/gas_mixture/environment)
	var/loc_temp = environment ? environment.temperature : T0C
	if(isobj(loc))
		var/obj/oloc = loc
		var/obj_temp = oloc.return_temperature()
		if(obj_temp != null)
			loc_temp = obj_temp
	else if(isspaceturf(get_turf(src)))
		var/turf/heat_turf = get_turf(src)
		loc_temp = heat_turf.temperature
	else if(isturf(loc) && iscarbon(src))
		var/turf/turf_loc = loc
		var/mob/living/carbon/carbon = src
		if(turf_loc.liquids?.liquid_state >= LIQUID_STATE_ANKLES)
			var/submergement_percent = SUBMERGEMENT_PERCENT(carbon, turf_loc.liquids)
			loc_temp = (loc_temp*(1-submergement_percent)) + (turf_loc.liquids.temperature * submergement_percent)
	return loc_temp

/// Change a mob's act-intent. Input the intent as an intent define or use "right"/"left"
/mob/living/verb/a_intent_change(input as text)
	set name = "a-intent"
	set hidden = 1

	if(!possible_a_intents || !possible_a_intents.len)
		return

	if(input in possible_a_intents)
		a_intent = input
	else
		var/current_intent = possible_a_intents.Find(a_intent)
		if(!current_intent)
			// Failsafe. Just in case some badmin was playing with VV.
			current_intent = 1

		if(input == INTENT_HOTKEY_RIGHT)
			current_intent += 1
		if(input == INTENT_HOTKEY_LEFT)
			current_intent -= 1

		// Handle looping
		if(current_intent < 1)
			current_intent = possible_a_intents.len
		if(current_intent > possible_a_intents.len)
			current_intent = 1

		a_intent = possible_a_intents[current_intent]

	hand_index_to_intent[active_hand_index] = a_intent
	hud_used?.intent_select?.icon_state = "[a_intent]"

/mob/living/set_combat_mode(new_mode, silent)
	if(combat_mode == new_mode)
		return
	. = combat_mode
	combat_mode = new_mode
	if(hud_used?.action_intent)
		hud_used.action_intent.update_appearance()
	if(silent)
		return
	if(combat_mode)
		playsound_local(src, 'sound/misc/ui_togglecombat.ogg', 25, FALSE, pressure_affected = FALSE) //Sound from interbay!
		if(mind?.combat_music)
			SSdroning.play_combat_music(mind.combat_music, client)
	else
		playsound_local(src, 'sound/misc/ui_toggleoffcombat.ogg', 25, FALSE, pressure_affected = FALSE) //Slightly modified version of the above
		if(mind?.combat_music)
			SSdroning.play_area_sound(get_area(src), client)


code:
/mob/living/death(gibbed)
	. = ..()
	if(!QDELETED(src))
		update_shock()
	if(.)
		if(client)
			if(deathsound_local)
				SEND_SOUND(client, deathsound_local)
			SSdroning.kill_droning(client)

/mob/living/revive(full_heal, admin_revive, excess_healing)
	. = ..()
	update_shock()

repo_name: AnalWerewolf/sundown

license: mit


code:
#define LIVING_UNARMED_ATTACK_BLOCKED(target_atom) (HAS_TRAIT(src, TRAIT_HANDS_BLOCKED) \
	|| SEND_SIGNAL(src, COMSIG_LIVING_UNARMED_ATTACK, target_atom, proximity_flag, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)

/mob/living/UnarmedHand(atom/attack_target, proximity_flag, list/modifiers)
	if(LIVING_UNARMED_ATTACK_BLOCKED(attack_target))
		return
	attack_target.attack_hand(src, modifiers)

/mob/living/UnarmedFoot(atom/attack_target, proximity_flag, list/modifiers)
	if(LIVING_UNARMED_ATTACK_BLOCKED(attack_target))
		return
	attack_target.attack_foot(src, modifiers)

/mob/living/UnarmedJaw(atom/attack_target, proximity_flag, list/modifiers)
	if(LIVING_UNARMED_ATTACK_BLOCKED(attack_target))
		return
	attack_target.attack_jaw(src, modifiers)

/mob/living/run_armor_check(def_zone = null, attack_flag = MELEE, absorb_text = null, soften_text = null, armour_penetration, penetrated_text, silent=FALSE, weak_against_armour = FALSE)
	var/armor = getarmor(def_zone, attack_flag)

	if(armor <= 0)
		return armor
	if(weak_against_armour && armor >= 0)
		armor *= ARMOR_WEAKENED_MULTIPLIER
	if(silent)
		return max(0, armor - armour_penetration)

	//the if "armor" check is because this is used for everything on /living, including humans
	if(armour_penetration)
		armor = max(0, armor - armour_penetration)
		if(penetrated_text)
			to_chat(src, span_userdanger("[penetrated_text]"))
		else
			to_chat(src, span_userdanger("My armor was penetrated!"))
	else if(armor >= 100)
		if(absorb_text)
			to_chat(src, span_notice("[absorb_text]"))
		else
			to_chat(src, span_notice("My armor absorbs the blow!"))
	else
		if(soften_text)
			to_chat(src, span_warning("[soften_text]"))
		else
			to_chat(src, span_warning("My armor softens the blow!"))
	return armor

#undef LIVING_UNARMED_ATTACK_BLOCKED


code:
/mob/living
	// Every living mob should have an attribute holder
	attributes = /datum/attribute_holder
	/// Chem effects
	var/list/chem_effects
	/// Combat style
	var/combat_style = CS_DEFAULT
	/// Dodge/parry
	var/dodge_parry = DP_PARRY
	/// Middle click special attacks
	var/special_attack = SPECIAL_ATK_NONE
	/// Intents
	var/a_intent
	var/list/possible_a_intents
	/// Intents, selected zones and throw mode are also saved on a hand by hand basis
	var/list/hand_index_to_intent
	var/list/hand_index_to_zone
	var/list/hand_index_to_throw
	/// Sprinting, fixeye, stamina, etc
	var/combat_flags = NONE
	/// Does it have FoV?
	var/has_field_of_vision = FALSE
	/// How many eyes does this mob have by default. This shouldn't change at runtime.
	var/default_num_eyes = 2
	/// How many eyes does this mob currently have. Should only be changed through set_num_eyes()
	var/num_eyes = 2
	/// How many usable eyes this mob currently has. Should only be changed through set_usable_eyes()
	var/usable_eyes = 2
	/// Used for the cringe filter
	var/bad_ic_count = 0


code:
/// Proc to modify the value of usable_legs and hook behavior associated to this event.
/mob/living/set_usable_legs(new_value)
	if(usable_legs == new_value)
		return
	. = usable_legs
	usable_legs = new_value

	if(new_value > .) // Gained leg usage.
		REMOVE_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
		REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
	else if(!(movement_type & (FLYING | FLOATING))) //Lost leg usage, not flying.
		if(!usable_legs)
			ADD_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
			if(!usable_hands)
				ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)

	if(usable_legs < default_num_legs)
		var/limbless_slowdown = (default_num_legs - usable_legs) * 1.5
		if(!usable_legs && usable_hands < default_num_hands)
			limbless_slowdown += (default_num_hands - usable_hands) * 3
		add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/limbless, multiplicative_slowdown = limbless_slowdown)
	else
		remove_movespeed_modifier(/datum/movespeed_modifier/limbless)


code:
/mob/living/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = FALSE)
	var/list/filter_result
	var/list/soft_filter_result
	if(client && !forced && !filterproof)
		//The filter doesn't act on the sanitized message, but the raw message.
		filter_result = is_ic_filtered(message)
		if(!filter_result)
			soft_filter_result = is_soft_ic_filtered(message)

	if(sanitize)
		message = replacetext(message, "", "") //replacing a zero width space with... nothing
		message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))

	if(!message || message == "")
		return

	if(filter_result && !filterproof)
		//The filter warning message shows the sanitized message though.
		var/warning_message = "A splitting headache prevents me from uttering vile words i planned to! The following terms repulse me:\n\""
		warning_message += config.ic_filter_regex.Replace(message, "<b>$1</b>")
		warning_message += "\""
		to_chat(src, warning_message)
		SSblackbox.record_feedback("tally", "ic_blocked_words", 1, lowertext(config.ic_filter_regex.match))

		bad_ic_count = clamp(bad_ic_count + 1, 1, 5)
		switch(bad_ic_count)
			if(1)
				SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "cringe", /datum/mood_event/cringe)
			if(2)
				SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "cringe", /datum/mood_event/ultracringe)
				client.bruh_and_kick()
			if(3)
				SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "cringe", /datum/mood_event/ultracringe)
				ADJUSTBRAINLOSS(src, 35)
				client.bruh_and_kick()
			if(4)
				SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "cringe", /datum/mood_event/ultracringe)
				ADJUSTBRAINLOSS(src, 75)
				client.bruh_and_kick()
			if(5)
				SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "cringe", /datum/mood_event/ultracringe)
				ADJUSTBRAINLOSS(src, 200)
				client.bruh_and_kick()
				visible_message(span_danger("[src] violently bleeds from [p_their()] nostrils, and falls limp on the ground."), \
							span_userdanger("I do not deserve the gift of life!"))
				death()
		return FALSE
	else if(soft_filter_result && !filterproof)
		if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
			SSblackbox.record_feedback("tally", "soft_ic_blocked_words", 1, lowertext(config.soft_ic_filter_regex.match))
			return
		message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[message]\"")
		log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[message]\"")
		SSblackbox.record_feedback("tally", "passed_soft_ic_blocked_words", 1, lowertext(config.soft_ic_filter_regex.match))
		. = ..()
	else
		bad_ic_count = max(bad_ic_count - 1, 0)
		. = ..()
	if(.)
		sound_hint()

/mob/living/treat_message(message)
	. = ..()
	if(length(.) && PUNCTUATION_FILTER_CHECK(.))
		. = trim(., MAX_MESSAGE_LEN - 1)
		. = "[.]."


code:
/// Hides FoV when perspective is changed
/mob/living/reset_perspective(atom/A)
	. = ..()
	if(client?.perspective != MOB_PERSPECTIVE)
		SEND_SIGNAL(src, COMSIG_FOV_HIDE)
	else
		SEND_SIGNAL(src, COMSIG_FOV_SHOW)

/// Checks if the user is incapacitated or on cooldown.
/mob/living/can_look_up()
	return !(incapacitated(ignore_restraints = TRUE))

/mob/living/look_up()
	if(client.perspective != MOB_PERSPECTIVE) //We are already looking up or down
		var/turf/eye = client.eye
		if(istype(eye) && (SSmapping.get_level(eye.z) < SSmapping.get_level(src.z)))
			end_look_down(FALSE)
		else
			end_look_up(FALSE)
		return
	if(!can_look_up())
		return
	changeNext_move(CLICK_CD_LOOK_UP)
	RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, .proc/stop_look_up, TRUE) //We stop looking up if we move
	RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/start_look_up, TRUE) //We start looking again after we move
	start_look_up(FALSE)

/mob/living/start_look_up(silent = TRUE)
	var/turf/ceiling = get_step_multiz(src, UP)
	if(!istype(ceiling)) //We are at the highest z-level
		if(!silent)
			to_chat(src, span_warning("I can't see through \the [ceiling] above me."))
		end_look_up(TRUE)
		return
	else if(!istransparentturf(ceiling)) //There is no turf we can look through above us
		var/turf/front_hole = get_step(ceiling, dir)
		if(istransparentturf(front_hole))
			ceiling = front_hole
		else
			var/list/checkturfs = block(locate(x-1,y-1,ceiling.z),locate(x+1,y+1,ceiling.z))-ceiling-front_hole //Try find hole near of us
			for(var/turf/checkhole in checkturfs)
				if(istransparentturf(checkhole))
					ceiling = checkhole
					break
		if(!istransparentturf(ceiling))
			if(!silent)
				to_chat(src, span_warning("I can't see through \the [ceiling] above me."))
			return
	if(!silent)
		to_chat(src, span_notice("I start looking up."))
	reset_perspective(ceiling)
	hud_used?.lookup?.name = "stop looking up"

/mob/living/stop_look_up()
	reset_perspective()
	hud_used?.lookup?.name = "look up"

/mob/living/end_look_up(silent = TRUE)
	stop_look_up()
	UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
	UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
	if(!silent)
		to_chat(src, span_notice("I stop looking up."))

/mob/living/look_down()
	if(client.perspective != MOB_PERSPECTIVE) //We are already looking up or down
		var/turf/eye = client.eye
		if(istype(eye) && (SSmapping.get_level(eye.z) < SSmapping.get_level(src.z)))
			end_look_down(FALSE)
		else
			end_look_up(FALSE)
		return
	if(!can_look_up()) //if we cant look up, we cant look down.
		return
	changeNext_move(CLICK_CD_LOOK_UP)
	RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, .proc/stop_look_down, TRUE) //We stop looking down if we move.
	RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/start_look_down, TRUE) //We start looking again after we move.
	start_look_down()

/mob/living/start_look_down(silent = TRUE)
	var/turf/floor = get_turf(src)
	var/turf/lower_level = get_step_multiz(src, DOWN)
	if(!lower_level) //We are at the lowest z-level.
		if(!silent)
			to_chat(src, span_warning("I can't see through \the [floor] below me."))
		end_look_down(TRUE)
		return
	else if(!istransparentturf(floor)) //There is no turf we can look through below us
		var/turf/front_hole = get_step(floor, dir)
		if(istransparentturf(front_hole))
			floor = front_hole
			lower_level = get_step_multiz(front_hole, DOWN)
		else
			var/list/checkturfs = block(locate(x-1,y-1,z),locate(x+1,y+1,z))-floor //Try find hole near of us
			for(var/turf/checkhole in checkturfs)
				if(istransparentturf(checkhole))
					floor = checkhole
					lower_level = get_step_multiz(checkhole, DOWN)
					break
		if(!istransparentturf(floor))
			if(!silent)
				to_chat(src, span_warning("I can't see through \the [floor] below me."))
			return
	if(!silent)
		to_chat(src, span_notice("I start looking down."))
	reset_perspective(lower_level)
	hud_used?.lookup?.name = "stop looking down"

/mob/living/stop_look_down()
	reset_perspective()
	hud_used?.lookup?.name = "look up"

/mob/living/end_look_down(silent = TRUE)
	stop_look_down()
	UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
	UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
	if(!silent)
		to_chat(src, span_notice("I stop looking down."))


code:
// Pain, ported from Baystation 12 and Eris, and cleaned up a bit.
/mob/living
	/// Pain (not taking other damage types into account) damage, generally a side effect of other types of damage
	var/painloss = 0
	/// Shock (pain taking into account other types of damage)
	var/traumatic_shock = 0
	/// Shock stage, as in how much our crit has progressed
	var/shock_stage = 0
	/// Last pain related message we have received - Used to prevent spam
	var/last_pain_message = ""
	/// Next time we are able to trigger custom_pain()
	var/next_pain_time = 0
	/// Next time we are able to send a custom_pain() chat message
	var/next_pain_message_time = 0

/// Hooks updating pain and shock to updatehealth
/mob/living/updatehealth()
	. = ..()
	if(status_flags & GODMODE)
		return
	update_pain()
	update_shock()

/// Updates pain value
/mob/living/proc/update_pain()
	painloss = getPainLoss()
	return painloss

/// Updates shock value
/mob/living/proc/update_shock()
	traumatic_shock = getShock(TRUE)
	return traumatic_shock

/// Can this mob get affected by shock?
/mob/living/proc/can_feel_pain()
	return FALSE

/// Called on Life(), handles constant pain (from other damages) and dynamic pain (painloss)
/mob/living/proc/handle_shock()
	return

/// Called on Life(), handles crit progression
/mob/living/proc/handle_shock_stage()
	return

/mob/proc/flash_pain(power)
	if(!hud_used?.pain_flash)
		return
	switch(power)
		if(5 to 25)
			flick("weakest_pain", hud_used.pain_flash)
		if(25 to 40)
			flick("weaker_pain", hud_used.pain_flash)
		if(40 to 75)
			flick("weak_pain", hud_used.pain_flash)
		if(75 to INFINITY)
			flick("pain", hud_used.pain_flash)
		else
			return

/mob/living/proc/custom_pain(message, power, forced, obj/item/bodypart/affecting, nopainloss)
	return


code:
/// Proc to modify the value of num_hands and hook behavior associated to this event.
/mob/living/proc/set_num_eyes(new_value)
	if(num_eyes == new_value)
		return
	. = num_eyes
	num_eyes = new_value

/// Proc to modify the value of usable_hands and hook behavior associated to this event.
/mob/living/proc/set_usable_eyes(new_value)
	if(usable_eyes == new_value)
		return
	. = usable_eyes
	usable_eyes = new_value


code:
/mob/living/ComponentInitialize()
	. = ..()
	RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_SPRINT_LOCKED), .proc/on_sprint_locked_gain)
	RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_SPRINT_LOCKED), .proc/on_sprint_locked_loss)

/mob/living/proc/on_sprint_locked_gain()
	disable_sprint()
	hud_used?.sprint?.update_appearance()
	hud_used?.mov_intent?.update_appearance()

/mob/living/proc/on_sprint_locked_loss()
	if(CHECK_BITFIELD(combat_flags, COMBAT_FLAG_SPRINT_ACTIVE))
		enable_sprint()
	hud_used?.sprint?.update_appearance()
	hud_used?.mov_intent?.update_appearance()

/mob/living/proc/toggle_sprint()
	if(HAS_TRAIT(src, TRAIT_SPRINT_LOCKED))
		if(!(combat_flags & COMBAT_FLAG_SPRINT_ACTIVE))
			to_chat(src, span_warning("I can't sprint!"))
		combat_flags ^= COMBAT_FLAG_SPRINT_ACTIVE
		hud_used?.sprint?.update_appearance()
		return
	combat_flags ^= COMBAT_FLAG_SPRINT_ACTIVE
	if(usable_legs < default_num_legs)
		to_chat(src, span_warning("Not enough legs!"))
		return
	if(CHECK_BITFIELD(combat_flags, COMBAT_FLAG_SPRINT_ACTIVE))
		playsound_local(src, 'modular_septic/sound/effects/sprintactivate.ogg', 60, FALSE, pressure_affected = FALSE)
		enable_sprint()
	else
		playsound_local(src, 'modular_septic/sound/effects/sprintdeactivate.ogg', 60, FALSE, pressure_affected = FALSE)
		disable_sprint()
	hud_used?.sprint?.update_appearance()
	hud_used?.mov_intent?.update_appearance()

/mob/living/proc/enable_sprint()
	if(m_intent != MOVE_INTENT_RUN)
		toggle_move_intent()
	combat_flags |= COMBAT_FLAG_SPRINTING
	add_movespeed_modifier(/datum/movespeed_modifier/sprinting, TRUE)

/mob/living/proc/disable_sprint()
	combat_flags &= ~COMBAT_FLAG_SPRINTING
	remove_movespeed_modifier(/datum/movespeed_modifier/sprinting, TRUE)

/mob/living/proc/doSprintLossTiles(tile_amount = 1)
	adjustStaminaLoss(tile_amount * GLOB.sprint_stamina_cost)


code:
//KNOCKDOWN
/mob/living/proc/KnockToFloor(silent = TRUE, ignore_canknockdown = FALSE, knockdown_amt = 1)
	return

/mob/living/proc/CombatKnockdown(stamina_damage, knockdown_amount, paralyze_amount, disarm = FALSE, ignore_canknockdown = FALSE)
	if(!stamina_damage)
		return
	return Paralyze((paralyze_amount ? paralyze_amount : stamina_damage))

//DAZE
/mob/living/proc/IsDazed() //If we're Dazed
	return has_status_effect(STATUS_EFFECT_DAZED)

/mob/living/proc/AmountDazed() //How many deciseconds remain in our Dazed status effect
	var/datum/status_effect/incapacitating/dazed/I = IsDazed()
	if(I)
		return I.duration - world.time
	return 0

/mob/living/proc/Daze(amount, updating = TRUE, ignore_canstun = FALSE) //Can't go below remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_DAZE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANSTUN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	if(absorb_stun(amount, ignore_canstun))
		return
	var/datum/status_effect/incapacitating/dazed/I = IsDazed()
	if(I)
		I.duration = max(world.time + amount, I.duration)
	else if(amount > 0)
		I = apply_status_effect(STATUS_EFFECT_DAZED, amount, updating)
	return I

/mob/living/proc/SetDazed(amount, updating = TRUE, ignore_canstun = FALSE) //Sets remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_DAZE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANSTUN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	var/datum/status_effect/incapacitating/dazed/I = IsDazed()
	if(amount <= 0)
		if(I)
			qdel(I)
	else
		if(absorb_stun(amount, ignore_canstun))
			return
		if(I)
			I.duration = world.time + amount
		else
			I = apply_status_effect(STATUS_EFFECT_DAZED, amount, updating)
	return I

/mob/living/proc/AdjustDazed(amount, updating = TRUE, ignore_canstun = FALSE) //Adds to remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_DAZE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANSTUN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	if(absorb_stun(amount, ignore_canstun))
		return
	var/datum/status_effect/incapacitating/dazed/I = IsDazed()
	if(I)
		I.duration += amount
	else if(amount > 0)
		I = apply_status_effect(STATUS_EFFECT_DAZED, amount, updating)
	return I

//STUMBLE
/mob/living/proc/IsStumble() //If we're stumbling
	return has_status_effect(STATUS_EFFECT_STUMBLE)

/mob/living/proc/AmountStumble() //How many deciseconds remain in our Dazed status effect
	var/datum/status_effect/incapacitating/dazed/stumble/I = IsStumble()
	if(I)
		return I.duration - world.time
	return 0

/mob/living/proc/Stumble(amount, updating = TRUE, ignore_canstun = FALSE) //Can't go below remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_STUMBLE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANKNOCKDOWN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	if(absorb_stun(amount, ignore_canstun))
		return
	var/datum/status_effect/incapacitating/dazed/stumble/I = IsStumble()
	if(I)
		I.duration = max(world.time + amount, I.duration)
	else if(amount > 0)
		I = apply_status_effect(STATUS_EFFECT_STUMBLE, amount, updating)
	return I

/mob/living/proc/SetStumble(amount, updating = TRUE, ignore_canstun = FALSE) //Sets remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_STUMBLE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANKNOCKDOWN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	var/datum/status_effect/incapacitating/dazed/stumble/I = IsStumble()
	if(amount <= 0)
		if(I)
			qdel(I)
	else
		if(absorb_stun(amount, ignore_canstun))
			return
		if(I)
			I.duration = world.time + amount
		else
			I = apply_status_effect(STATUS_EFFECT_STUMBLE, amount, updating)
	return I

/mob/living/proc/AdjustStumble(amount, updating = TRUE, ignore_canstun = FALSE) //Adds to remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_STUMBLE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANKNOCKDOWN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	if(absorb_stun(amount, ignore_canstun))
		return
	var/datum/status_effect/incapacitating/dazed/stumble/I = IsStumble()
	if(I)
		I.duration += amount
	else if(amount > 0)
		I = apply_status_effect(STATUS_EFFECT_STUMBLE, amount, updating)
	return I

//HEAD RAPE
/mob/living/proc/IsHeadRape()
	return has_status_effect(STATUS_EFFECT_HEADRAPE)

/mob/living/proc/AmountHeadRape()
	var/datum/status_effect/incapacitating/headrape/I = IsHeadRape()
	if(I)
		return I.duration - world.time
	return 0

/mob/living/proc/HeadRape(amount, updating = TRUE, ignore_canstun = FALSE) //Can't go below remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_HEADRAPE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANSTUN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	if(absorb_stun(amount, ignore_canstun))
		return
	var/datum/status_effect/incapacitating/headrape/I = IsHeadRape()
	if(I)
		I.duration = max(world.time + CEILING(amount, 4 SECONDS), I.duration)
	else if(amount > 0)
		I = apply_status_effect(STATUS_EFFECT_HEADRAPE, CEILING(amount, 4), updating)
	return I

/mob/living/proc/SetHeadRape(amount, updating = TRUE, ignore_canstun = FALSE) //Sets remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_HEADRAPE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANSTUN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	var/datum/status_effect/incapacitating/headrape/I = IsHeadRape()
	if(amount <= 0)
		if(I)
			qdel(I)
	else
		if(absorb_stun(amount, ignore_canstun))
			return
		if(I)
			I.duration = world.time + amount
		else
			I = apply_status_effect(STATUS_EFFECT_HEADRAPE, CEILING(amount, 4 SECONDS), updating)
	return I

/mob/living/proc/AdjustHeadRape(amount, updating = TRUE, ignore_canstun = FALSE) //Adds to remaining duration
	if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_HEADRAPE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN)
		return
	if(!ignore_canstun && (!(status_flags & CANSTUN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)))
		return
	if(absorb_stun(amount, ignore_canstun))
		return
	var/datum/status_effect/incapacitating/headrape/I = IsHeadRape()
	if(I)
		I.duration += amount
	else if(amount > 0)
		I = apply_status_effect(STATUS_EFFECT_HEADRAPE, CEILING(amount, 4 SECONDS), updating)
	return I


code:
/mob/living/verb/succumb(whispered as null)
	set hidden = TRUE
	if(!CAN_SUCCUMB(src))
		to_chat(src, text="I am unable to succumb to death! This life continues.", type=MESSAGE_TYPE_INFO)
		return
	log_message("Has [whispered ? "whispered his final words" : "succumbed to death"] with [round(health, 0.1)] points of health!", LOG_ATTACK)
	adjustOxyLoss(src.maxHealth)
	ADJUSTBRAINLOSS(src, src.maxHealth)
	if(!whispered)
		to_chat(src, span_dead("I have given up life and succumbed to death."))
	death()
	updatehealth()


code:
// Wielding procs
/mob/living/proc/wield_active_hand()
	var/obj/item/active = get_active_held_item()
	if(istype(active))
		active.wield_act(src)
	else
		to_chat(src, span_warning("You have nothing to wield!"))

/mob/living/proc/wield_ui_on()
	if(hud_used?.wield)
		hud_used.wield.active = TRUE
		hud_used.wield.update_appearance()
		return TRUE

/mob/living/proc/wield_ui_off()
	if(hud_used?.wield)
		hud_used.wield.active = FALSE
		hud_used.wield.update_appearance()
		return TRUE


code:
/mob
	plane = MOB_PLANE

/mob/Initialize(mapload)
	. = ..()
	set_hydration(rand(HYDRATION_LEVEL_START_MIN, HYDRATION_LEVEL_START_MAX))
	// If we have an attribute holder, lets get that W
	if(ispath(attributes))
		AttributeInitialize()

//No stumble handling with mobs - handled by throw impact
/mob/on_rammed(mob/living/carbon/C)
	return

/mob/return_screentip(mob/user, params)
	if(flags_1 & NO_SCREENTIPS_1)
		return ""
	return SCREENTIP_MOB(uppertext(name))

/mob/verb/examinate(atom/examinify as mob|obj|turf in fov_view())
	set name = "Examine"
	set category = "IC"

	if(isturf(examinify) && !(sight & SEE_TURFS) && !(examinify in fov_view(client ? client.view : world.view, src)))
		return

	if(is_blind() && !blind_examine_check(examinify)) //blind people see things differently (through touch)
		return

	face_atom(examinify)
	var/flags = SEND_SIGNAL(src, COMSIG_MOB_EXAMINATE, examinify)
	if(flags & COMPONENT_DENY_EXAMINATE)
		if(flags & COMPONENT_EXAMINATE_BLIND)
			to_chat(src, "Something is there but you can't see it!")
		return
	var/list/result
	if(client)
		LAZYINITLIST(client.recent_examines)
		var/ref_to_atom = ref(examinify)
		var/examine_time = LAZYACCESS(client.recent_examines, ref_to_atom)
		if(examine_time && (world.time - examine_time < EXAMINE_MORE_WINDOW))
			result = examinify.examine_more(src)
			if(!LAZYLEN(result))
				result = list(span_notice("I examine [examinify] closer, but find nothing of interest..."))
			handle_eye_contact(examinify)
		else
			result = examinify.examine(src)
			client.recent_examines[ref_to_atom] = world.time // set the value to when the examine cooldown ends
			addtimer(CALLBACK(src, .proc/clear_from_recent_examines, ref_to_atom), RECENT_EXAMINE_MAX_WINDOW)
			handle_eye_contact(examinify)
	else
		result = examinify.examine(src) // if a tree is examined but no client is there to see it, did the tree ever really exist?

	if(result)
		to_chat(src, "
[result.Join("\n")]
")

///Attributes
/mob/proc/AttributeInitialize()
	attributes = new attributes(src)

///Adjust the hydration of a mob
/mob/proc/adjust_hydration(change)
	hydration = max(0, hydration + change)

///Force set the mob hydration
/mob/proc/set_hydration(change)
	hydration = max(0, change)


code:
/mob/proc/UnarmedHand(atom/attack_target, proximity_flag, list/modifiers)
	return

/mob/proc/UnarmedFoot(atom/attack_target, proximity_flag, list/modifiers)
	return

/mob/proc/UnarmedJaw(atom/attack_target, proximity_flag, list/modifiers)
	return

/mob/proc/MiddleClickJump(atom/jump_target, proximity_flag, list/modifiers)
	return


code:
/mob
	/// Skill holder
	var/datum/attribute_holder/attributes
	/// Sound we play to the player who controls us on death
	var/deathsound_local = sound('modular_septic/sound/effects/flatline.ogg', FALSE, 0, CHANNEL_EAR_RING, 100)
	/// Works like client.movement_locked, but handled mob-wise
	var/movement_locked = FALSE
	/// Hydration level of the mob
	var/hydration = HYDRATION_LEVEL_START_MIN // Randomised in Initialize
	/// Hydration satiation level of the mob
	var/hydro_satiety = 0 //Carbon


code:
///Validate the client's mob has a valid zone selected
/client/check_has_body_select()
	return mob?.hud_used?.zone_select && istype(mob.hud_used.zone_select, /atom/movable/screen/zone_sel)

//bodypart selection verbs - Cyberboss
//9: r_eye - l_eye
//8: head - face - r_eye - l_eye - mouth
//7: mouth - face
//4: r_arm - r_hand
//5: chest - neck
//6: l_arm - l_hand
//1: r_leg - r_foot
//2: groin
//3: l_leg - l_foot

///Hidden verb to target the head, bound to 8
/client/verb/body_toggle_head()
	set name = "body-toggle-head"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/next_in_line
	switch(mob.zone_selected)
		if(BODY_ZONE_HEAD)
			next_in_line = BODY_ZONE_PRECISE_FACE
		if(BODY_ZONE_PRECISE_FACE)
			next_in_line = BODY_ZONE_PRECISE_R_EYE
		if(BODY_ZONE_PRECISE_R_EYE)
			next_in_line = BODY_ZONE_PRECISE_L_EYE
		if(BODY_ZONE_PRECISE_L_EYE)
			next_in_line = BODY_ZONE_PRECISE_MOUTH
		else
			next_in_line = BODY_ZONE_HEAD

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(next_in_line, mob)

///Hidden verb to target the eyes, bound to 7
/client/verb/body_eyes()
	set name = "body-eyes"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(mob.zone_selected == BODY_ZONE_PRECISE_R_EYE ? BODY_ZONE_PRECISE_L_EYE : BODY_ZONE_PRECISE_R_EYE, mob)

///Hidden verb to target the mouth, bound to 9
/client/verb/body_mouth()
	set name = "body-mouth"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(mob.zone_selected == BODY_ZONE_PRECISE_MOUTH ? BODY_ZONE_PRECISE_FACE : BODY_ZONE_PRECISE_MOUTH, mob)

///Hidden verb to target the chest, bound to 5
/client/verb/body_chest()
	set name = "body-chest"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(mob.zone_selected == BODY_ZONE_CHEST ? BODY_ZONE_PRECISE_NECK : BODY_ZONE_CHEST, mob)

///Hidden verb to target the groin, bound to 2
/client/verb/body_groin()
	set name = "body-groin"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(BODY_ZONE_PRECISE_GROIN, mob)

///Hidden verb to target the right arm, bound to 4
/client/verb/body_r_arm()
	set name = "body-r-arm"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(mob.zone_selected == BODY_ZONE_R_ARM ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_R_ARM, mob)

///Hidden verb to target the left arm, bound to 6
/client/verb/body_l_arm()
	set name = "body-l-arm"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(mob.zone_selected == BODY_ZONE_L_ARM ? BODY_ZONE_PRECISE_L_HAND : BODY_ZONE_L_ARM, mob)

///Hidden verb to target the right leg, bound to 1
/client/verb/body_r_leg()
	set name = "body-r-leg"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(mob.zone_selected == BODY_ZONE_R_LEG ? BODY_ZONE_PRECISE_R_FOOT : BODY_ZONE_R_LEG, mob)

///Hidden verb to target the left leg, bound to 3
/client/verb/body_l_leg()
	set name = "body-l-leg"
	set hidden = TRUE

	if(!check_has_body_select())
		return

	var/atom/movable/screen/zone_sel/selector = mob.hud_used.zone_select
	selector.set_selected_zone(mob.zone_selected == BODY_ZONE_L_LEG ? BODY_ZONE_PRECISE_L_FOOT : BODY_ZONE_L_LEG, mob)


code:
/// Move a mob between z levels, if it's valid to move z's on this turf
/mob/zMove(dir, feedback = FALSE, ventcrawling = FALSE)
	if(dir != UP && dir != DOWN)
		return FALSE
	if(incapacitated())
		if(feedback)
			to_chat(src, span_warning("I can't do that right now!"))
		return FALSE
	var/turf/target = get_step_multiz(src, dir)
	if(!istype(target))
		if(feedback)
			to_chat(src, span_warning("There's nowhere to go in that direction!"))
		return FALSE
	if(!canZMove(dir, target) && !ventcrawling)
		if(feedback)
			to_chat(src, span_warning("I can't move there!"))
		return FALSE
	if(!ventcrawling) //let this be handled in atmosmachinery.dm
		forceMove(target)
	else
		var/obj/machinery/atmospherics/pipe = loc
		pipe.relaymove(src, dir)
	return TRUE

/// Moves a mob upwards in z level
/mob/verb/up()
	set name = "Move Upwards"
	set category = "IC.ZTravel"

	if(HAS_TRAIT_FROM(src, TRAIT_MOVE_FLOATING, CLINGING_TRAIT))
		to_chat(src, span_warning("Can't do that while climbing."))
		return

	var/turf/current_turf = get_turf(src)
	var/turf/above_turf = SSmapping.get_turf_above(current_turf)
	var/ventcrawling_mob = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING)

	if(above_turf && !ventcrawling_mob && (can_zFall(above_turf, 1, current_turf, DOWN) && (above_turf.can_zFall(src, 1, current_turf))))
		to_chat(src, span_warning("I can't. I will fall back down."))
		return

	if(zMove(UP, TRUE, ventcrawling_mob))
		to_chat(src, span_notice("I move upwards."))

/// Moves a mob down a z level
/mob/verb/down()
	set name = "Move Downwards"
	set category = "IC.ZTravel"

	if(HAS_TRAIT_FROM(src, TRAIT_MOVE_FLOATING, CLINGING_TRAIT))
		to_chat(src, span_warning("Can't do that while climbing."))
		return

	var/ventcrawling_mob = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING)

	if(zMove(DOWN, TRUE, ventcrawling_mob))
		to_chat(src, span_notice("I move downwards."))


code:
/datum/movespeed_modifier/limb_efficiency
	movetypes = GROUND
	variable = TRUE


code:
/datum/movespeed_modifier/thirst
	variable = TRUE

/datum/movespeed_modifier/traumatic_shock
	variable = TRUE

/datum/movespeed_modifier/shock_stage
	multiplicative_slowdown = SHOCK_STAGE_SLOWDOWN

/datum/movespeed_modifier/cardiac_arrest
	multiplicative_slowdown = CARDIAC_ARREST_SLOWDOWN
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/sprinting
	multiplicative_slowdown = -SPRINT_DEFAULT_SPEED_INCREASE
	movetypes = GROUND
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/sprinting/proc/sync()
	var/mod = CONFIG_GET(number/movedelay/sprint_speed)
	multiplicative_slowdown = isnum(mod) ? -mod : initial(multiplicative_slowdown)

/datum/movespeed_modifier/grab_slowdown/aggressive
	multiplicative_slowdown = 3

/datum/movespeed_modifier/grab_slowdown/neck
	multiplicative_slowdown = 6

/datum/movespeed_modifier/grab_slowdown/kill
	multiplicative_slowdown = 6

/datum/movespeed_modifier/chemical_effect/speedboost
	variable = TRUE
	blacklisted_movetypes = (FLOATING)

/datum/movespeed_modifier/status_effect/water_slowdown
	variable = TRUE
	blacklisted_movetypes = (FLYING|FLOATING)


code:
/datum/movespeed_modifier/carry_weight
	variable = TRUE


code:
/datum/pollution
	/// Reference to the turf we're on
	var/turf/open/my_turf
	/// List of all pollutant types to their amount
	var/list/pollutants = list()
	/// Total amount
	var/total_amount = 0
	/// Height of the pollution, used to create a sandpiling effect
	var/height = 0
	/// The overlay we are managing
	var/mutable_appearance/managed_overlay

/datum/pollution/New(turf/open/passed_turf)
	. = ..()
	my_turf = passed_turf
	my_turf.pollution = src
	REGISTER_POLLUTION(src)

/datum/pollution/Destroy()
	if(managed_overlay)
		my_turf.overlays -= managed_overlay
	REMOVE_POLLUTION_CURRENTRUN(src)
	SET_UNACTIVE_POLLUTION(src)
	UNREGISTER_POLLUTION(src)
	my_turf.pollution = null
	return ..()

/datum/pollution/proc/touch_act(mob/living/carbon/victim)
	if(!victim.can_inject())
		return
	var/list/singleton_cache = SSpollution.singletons
	for(var/type in pollutants)
		var/datum/pollutant/pollutant = singleton_cache[type]
		if(!(pollutant.pollutant_flags & POLLUTANT_TOUCH_ACT))
			continue
		var/amount = pollutants[type]
		pollutant.touch_act(victim, amount)

/datum/pollution/proc/breathe_act(mob/living/carbon/victim)
	var/list/singleton_cache = SSpollution.singletons
	for(var/type in pollutants)
		var/datum/pollutant/pollutant = singleton_cache[type]
		if(!(pollutant.pollutant_flags & POLLUTANT_BREATHE_ACT))
			continue
		var/amount = pollutants[type]
		pollutant.breathe_act(victim, amount)

/// When a user smells this pollution
/datum/pollution/proc/smell_act(mob/living/sniffer)
	var/list/singleton_cache = SSpollution.singletons
	var/datum/pollutant/dominant_pollutant
	var/dominant_smell_power
	for(var/type in pollutants)
		var/datum/pollutant/pollutant = singleton_cache[type]
		if(!(pollutant.pollutant_flags & POLLUTANT_SMELL))
			continue
		var/smelly_power = pollutant.smell_intensity * pollutants[type]
		if(smelly_power < POLLUTANT_SMELL_THRESHOLD)
			continue
		if(!dominant_pollutant || smelly_power > dominant_smell_power)
			dominant_pollutant = pollutant
			dominant_smell_power = smelly_power
	if(!dominant_pollutant)
		return

	var/smell_string
	switch(dominant_smell_power)
		if(0 to POLLUTANT_SMELL_NORMAL)
			if(sniffer.mob_biotypes & MOB_ROBOTIC)
				smell_string = "Your sensors detect trace amounts of [dominant_pollutant.scent] in the air."
			else
				smell_string = "The subtle [dominant_pollutant.descriptor] of [dominant_pollutant.scent] tickles your nose..."
		if(POLLUTANT_SMELL_NORMAL to POLLUTANT_SMELL_STRONG)
			if(sniffer.mob_biotypes & MOB_ROBOTIC)
				smell_string = "Your sensors pick up the presence of [dominant_pollutant.scent] in the air."
			else
				smell_string = "The [dominant_pollutant.descriptor] of [dominant_pollutant.scent] fills the air."
		if(POLLUTANT_SMELL_STRONG to INFINITY)
			if(sniffer.mob_biotypes & MOB_ROBOTIC)
				smell_string = "Your sensors pick up an intense concentration of [dominant_pollutant.scent]."
			else
				smell_string = "The unmistakable [dominant_pollutant.descriptor] of [dominant_pollutant.scent] bombards your nostrils."

	if(dominant_pollutant.descriptor == SCENT_DESC_ODOR)
		to_chat(sniffer, span_warning(smell_string))
	else
		to_chat(sniffer, span_notice(smell_string))

/datum/pollution/proc/scrub_amount(amount_to_scrub, update_active = TRUE, planetary_multiplier = FALSE)
	if(amount_to_scrub >= total_amount)
		qdel(src)
		return
	if(planetary_multiplier && my_turf.planetary_atmos) //Dissipate faster on planetary atmos
		amount_to_scrub *= 2
	for(var/type in pollutants)
		pollutants[type] -= amount_to_scrub * pollutants[type] / total_amount
	total_amount -= amount_to_scrub
	update_height()
	handle_overlay()
	if(update_active)
		my_turf.update_adjacent_pollution()

/datum/pollution/proc/add_pollutant(polutant_type, amount)
	if(!pollutants[polutant_type])
		pollutants[polutant_type] = 0
	pollutants[polutant_type] += amount
	total_amount += amount
	update_height()
	handle_overlay()
	SET_ACTIVE_POLLUTION(src)

/datum/pollution/proc/add_pollutant_list(list/passed_pollutants)
	for(var/polutant_type in passed_pollutants)
		if(!pollutants[polutant_type])
			pollutants[polutant_type] = 0
		pollutants[polutant_type] += passed_pollutants[polutant_type]
		total_amount += passed_pollutants[polutant_type]
	update_height()
	handle_overlay()
	SET_ACTIVE_POLLUTION(src)

/datum/pollution/proc/update_height()
	height = calculate_height(total_amount)

/datum/pollution/proc/calculate_height(passed_amount)
	return CEILING(passed_amount / POLLUTION_HEIGHT_DIVISOR, 1)

/datum/pollution/proc/process_cell()
	if(height <= 1)
		SET_UNACTIVE_POLLUTION(src)
		return
	var/list/sharing_turfs = list()
	var/list/already_processed_cache = SSpollution.processed_this_run
	var/list/potential_activers = list()
	for(var/turf/open/open_turf as anything in my_turf.atmos_adjacent_turfs)
		if(!already_processed_cache[open_turf])
			if(can_share_pollution_with(open_turf))
				sharing_turfs[open_turf] = TRUE
			else
				potential_activers[open_turf] = TRUE
	if(!sharing_turfs.len)
		SET_UNACTIVE_POLLUTION(src)
		return
	sharing_turfs[my_turf] = TRUE
	//Now we've gotten all the turfs that should share pollution. Gather their total pollutions and split evenly
	var/list/total_share_pollutants = list()
	var/total_share_amount = 0
	for(var/turf/open/open_turf as anything in sharing_turfs)
		if(!open_turf.pollution)
			continue
		var/datum/pollution/cached_pollution = open_turf.pollution
		for(var/type in cached_pollution.pollutants)
			if(!total_share_pollutants[type])
				total_share_pollutants[type] = 0
			total_share_pollutants[type] += cached_pollution.pollutants[type]
			total_share_amount += cached_pollution.pollutants[type]

	for(var/type in total_share_pollutants)
		total_share_pollutants[type] /= sharing_turfs.len
	total_share_amount /= sharing_turfs.len
	var/new_heights = calculate_height(total_share_amount)
	var/mutable_appearance/new_overlay = get_overlay(total_share_pollutants, total_share_amount)
	for(var/turf/open/open_turf as anything in sharing_turfs)
		assert_pollution(open_turf)
		var/datum/pollution/cached_pollution = open_turf.pollution
		if(cached_pollution.managed_overlay)
			cached_pollution.my_turf.underlays -= cached_pollution.managed_overlay
		cached_pollution.managed_overlay = new_overlay
		if(new_overlay)
			cached_pollution.my_turf.underlays += new_overlay
		cached_pollution.pollutants = total_share_pollutants.Copy()
		cached_pollution.total_amount = total_share_amount
		cached_pollution.height = new_heights
		SET_ACTIVE_POLLUTION(cached_pollution)
	for(var/turf/open/open_turf as anything in potential_activers)
		if(open_turf.pollution && open_turf.pollution.can_share_pollution_with(my_turf))
			SET_ACTIVE_POLLUTION(open_turf.pollution)

/datum/pollution/proc/can_share_pollution_with(turf/open/shareto)
	if(!shareto.pollution)
		return TRUE
	if(shareto.pollution.height + 1 < height)
		return TRUE
	return FALSE

/datum/pollution/proc/assert_pollution(turf/open/to_assert)
	if(!to_assert.pollution)
		new /datum/pollution(to_assert)

/datum/pollution/proc/handle_overlay()
	if(managed_overlay)
		my_turf.underlays -= managed_overlay
	managed_overlay = get_overlay(pollutants, total_amount)
	if(managed_overlay)
		my_turf.underlays += managed_overlay

/// Probably the most costly thing happening here
/datum/pollution/proc/get_overlay(list/pollutant_list, total_amount)
	var/datum/pollutant/pollutant
	var/total_thickness
	if(pollutant_list.len == 1)
		pollutant = SSpollution.singletons[pollutant_list[1]]
		if(!(pollutant.pollutant_flags & POLLUTANT_APPEARANCE))
			return
		total_thickness = total_amount * pollutant.thickness
	else
		var/list/pollutant_cache = SSpollution.singletons
		var/datum/pollutant/iterated_pollutant
		var/calc_thickness
		for(var/pollutant_type in pollutant_list)
			iterated_pollutant = pollutant_cache[pollutant_type]
			if(!(iterated_pollutant.pollutant_flags & POLLUTANT_APPEARANCE))
				continue
			calc_thickness = pollutant_list[pollutant_type] * iterated_pollutant.thickness
			if(!pollutant || calc_thickness > total_thickness)
				pollutant = iterated_pollutant
				total_thickness = calc_thickness

	if(!total_thickness || total_thickness < POLLUTANT_APPEARANCE_THICKNESS_THRESHOLD)
		return

	var/mutable_appearance/overlay = mutable_appearance('modular_septic/icons/effects/pollution/pollution.dmi', "smoke", FLY_LAYER, POLLUTION_PLANE, appearance_flags = KEEP_APART|RESET_TRANSFORM|RESET_COLOR)
	overlay.pixel_x = -32
	overlay.pixel_y = -32
	overlay.alpha = FLOOR(pollutant.alpha * total_thickness * THICKNESS_ALPHA_COEFFICIENT, 1)
	overlay.color = pollutant.color
	return overlay

/// Atmos adjacency has been updated on this turf, see if it affects any of our pollutants
/turf/proc/update_adjacent_pollution()
	for(var/turf/open/open_turf as anything in atmos_adjacent_turfs)
		if(open_turf.pollution)
			SET_ACTIVE_POLLUTION(open_turf.pollution)


code:
/turf
	/// Pollution datum of this turf
	var/datum/pollution/pollution

/turf/proc/pollute_turf(pollution_type, amount, cap)
	if(!pollution)
		pollution = new(src)
	pollution.add_pollutant(pollution_type, amount)

/turf/proc/pollute_list_turf(list/pollutions, cap)
	if(!pollution)
		pollution = new(src)
	if(cap && pollution.total_amount >= cap)
		return
	pollution.add_pollutant_list(pollutions)


code:
/obj/item/ammo_casing/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/fired_from)
	. = ..()
	if(!loaded_projectile)
		return
	if(isgun(fired_from))
		var/obj/item/gun/gun = fired_from
		loaded_projectile.stat_ranged = gun.stat_ranged
		loaded_projectile.skill_ranged = gun.skill_ranged
		loaded_projectile.specialty_ranged = gun.specialty_ranged
	if(!zone_override && user.attributes)
		var/modifier = 0
		var/attributes_used = 0
		if(loaded_projectile.stat_ranged)
			modifier += GET_MOB_ATTRIBUTE_VALUE(user, loaded_projectile.stat_ranged)
			attributes_used += 1
		if(loaded_projectile.skill_ranged)
			modifier += GET_MOB_ATTRIBUTE_VALUE(user, loaded_projectile.skill_ranged)
			attributes_used += 1
		if(loaded_projectile.specialty_ranged)
			modifier += GET_MOB_ATTRIBUTE_VALUE(user, loaded_projectile.specialty_ranged)
		var/diceroll = user.diceroll(modifier, 10*attributes_used, attributes_used, 20)
		//Change zone only on crit fails
		if(diceroll >= DICE_FAILURE)
			loaded_projectile.def_zone = user.zone_selected
		else
			loaded_projectile.def_zone = ran_zone(user.zone_selected, 0)
	if(ishuman(user))
		loaded_projectile.range = get_dist(loaded_projectile.starting, get_turf(loaded_projectile.original))


code:
/obj/item/ammo_casing/shotgun/buckshot
	name = "buckshot shell"
	desc = "A 12 gauge buckshot shell."
	icon_state = "gshell"
	projectile_type = /obj/projectile/bullet/pellet/shotgun_buckshot
	pellets = 6
	variance = 10


code:
/obj/item/ammo_box
	carry_weight = 3

/obj/item/ammo_box/get_carry_weight()
	. = ..()
	for(var/obj/item/ammo_casing/casing as anything in stored_ammo)
		. += casing.get_carry_weight()


code:
/obj/item/ammo_box/magazine
	carry_weight = 0.5


code:
/obj/item/ammo_casing
	carry_weight = 0.02
	var/obj/item/ammo_box/magazine/ammo_stack = /obj/item/ammo_box/magazine/ammo_stack

/obj/item/ammo_casing/attackby(obj/item/I, mob/user, params)
	. = ..()
	if(istype(I, /obj/item/ammo_casing))
		var/obj/item/ammo_casing/AC = I
		if(!AC.ammo_stack)
			to_chat(user, span_warning("[AC] can't be stacked."))
			return
		if(!ammo_stack)
			to_chat(user, span_warning("[src] can't be stacked."))
			return
		if(caliber != AC.caliber)
			to_chat(user, span_warning("No... Stacking different calibers would just make it confusing later on."))
			return
		if((loaded_projectile && AC.loaded_projectile) || (!loaded_projectile && !AC.loaded_projectile)) //FUCK YOU BYOOOOND
			to_chat(user, span_warning("Bleh... Stacking spent casings with unspent ones sounds like a terrible idea."))

		var/obj/item/ammo_box/magazine/ammo_stack/AS = new(get_turf(src))
		AS.name = "[capitalize(caliber)] rounds"
		AS.caliber = caliber
		AS.give_round(src)
		AS.give_round(AC)
		user.put_in_hands(AS)
		to_chat(user, span_notice("[src] has been stacked with [AC]."))

//The ammo stack itself
/obj/item/ammo_box/magazine/ammo_stack
	name = "ammo stack"
	desc = "A stack of ammo."
	icon = 'modular_septic/icons/obj/ammo/stacks.dmi'
	icon_state = "nothing"
	max_ammo = 12
	multiple_sprites = FALSE
	start_empty = TRUE
	multiload = FALSE

/obj/item/ammo_box/magazine/ammo_stack/update_overlays()
	. = ..()
	cut_overlays()
	for(var/casing in stored_ammo)
		var/obj/item/ammo_casing/AC = casing
		var/mutable_appearance/comicao = image(AC.icon, src, AC.icon_state)
		comicao.pixel_x = rand(0, 8)
		comicao.pixel_y = rand(0, 8)
		comicao.transform = comicao.transform.Turn(rand(0, 360))
		add_overlay(comicao)

/obj/item/ammo_box/magazine/ammo_stack/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
	. = ..()
	while(length(stored_ammo))
		var/obj/item/I = get_round(FALSE)
		I.forceMove(loc)
		I.throw_at(loc)
	check_for_del()

/obj/item/ammo_box/magazine/ammo_stack/get_round(keep)
	. = ..()
	update_icon()
	check_for_del()

/obj/item/ammo_box/magazine/ammo_stack/give_round(obj/item/ammo_casing/R, replace_spent)
	. = ..()
	update_icon()
	check_for_del()

/obj/item/ammo_box/magazine/ammo_stack/handle_atom_del(atom/A)
	. = ..()
	check_for_del()

/obj/item/ammo_box/magazine/ammo_stack/empty_magazine()
	. = ..()
	check_for_del()

/obj/item/ammo_box/magazine/ammo_stack/update_ammo_count()
	. = ..()
	check_for_del()

/obj/item/ammo_box/magazine/ammo_stack/proc/check_for_del()
	. = FALSE
	if(ammo_count() <= 0 && !QDELETED(src))
		qdel(src)
		return TRUE


code:
/obj/item/ammo_box/magazine/glock9mm
	name = "gook-17 magazine (9mm)"
	icon = 'modular_septic/icons/obj/ammo/pistol.dmi'
	icon_state = "pistol9mm-16"
	base_icon_state = "pistol9mm"
	ammo_type = /obj/item/ammo_casing/c9mm
	caliber = CALIBER_9MM
	max_ammo = 16

/obj/item/ammo_box/magazine/glock9mm/update_icon_state()
	. = ..()
	icon_state = "[base_icon_state]-[round(ammo_count(), 2)]"

/obj/item/ammo_box/magazine/glock9mm/dick
	name = "gook-17 DICK magazine (9mm)"
	icon = 'modular_septic/icons/obj/ammo/pistol.dmi'
	icon_state = "pistoldick9mm-16"
	base_icon_state = "pistoldick9mm"
	ammo_type = /obj/item/ammo_casing/c9mm
	caliber = CALIBER_9MM
	max_ammo = 33

/obj/item/ammo_box/magazine/glock9mm/dick/update_icon_state()
	. = ..()
	icon_state = "[base_icon_state]-[round(ammo_count(), 3)]"

/obj/item/ammo_box/magazine/glock10mm
	name = "gook-20 magazine (10mm)"
	icon = 'modular_septic/icons/obj/ammo/pistol.dmi'
	icon_state = "pistol10mm_l-8"
	base_icon_state = "pistol10mm_l"
	ammo_type = /obj/item/ammo_casing/c10mm
	caliber = CALIBER_10MM
	max_ammo = 8

/obj/item/ammo_box/magazine/glock10mm/update_icon_state()
	. = ..()
	icon_state = "[base_icon_state]-[round(ammo_count(), 2)]"


code:
/obj/item/ammo_box/magazine/saigadrum
	name = "REMIS-12 drum magazine"
	desc = "A drumm mamgazinn for the remmseries 12 shotgum."
	icon_state = "m12gb"
	base_icon_state = "m12gb"
	ammo_type = /obj/item/ammo_casing/shotgun/buckshot
	caliber = CALIBER_SHOTGUN
	max_ammo = 18

/obj/item/ammo_box/magazine/saigadrum/update_icon_state()
	. = ..()
	icon_state = "[base_icon_state]-[ammo_count(TRUE) ? max_ammo : 0]"


code:
/obj/item/ammo_box/magazine/m45vector
	name = "KeKtor magazine (.45)"
	icon_state = "c20r45-24"
	base_icon_state = "c20r45"
	ammo_type = /obj/item/ammo_casing/c45
	caliber = CALIBER_45
	max_ammo = 40

/obj/item/ammo_box/magazine/m45vector/update_icon_state()
	. = ..()
	icon_state = "[base_icon_state]"

/obj/item/ammo_box/magazine/ppsh9mm
	name = "Mamasha magazine (9mm)"
	icon_state = "smg9mm-42"
	base_icon_state = "smg9mm"
	ammo_type = /obj/item/ammo_casing/c9mm
	caliber = CALIBER_9MM
	max_ammo = 32

/obj/item/ammo_box/magazine/ppsh9mm/update_icon_state()
	. = ..()
	icon_state = "[base_icon_state]-[ammo_count() ? 62 : 0]"


code:
/obj/item/gun/ballistic
	carry_weight = 2.5

/obj/item/gun/ballistic/get_carry_weight()
	. = ..()
	if(istype(magazine))
		. += magazine.get_carry_weight()

/obj/item/gun/ballistic/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params)
	. = ..()
	if(internal_magazine || !isliving(usr) || !usr.Adjacent(src))
		return
	var/mob/living/user = usr
	if(istype(over, /atom/movable/screen/inventory/hand))
		eject_magazine(user)


code:
/obj/item/gun/ballistic/automatic/pistol/glock
	name = "\improper Gook-17"
	desc = "A reliable 9mm handgun with a large magazine capacity."
	icon = 'modular_septic/icons/obj/guns/pistol.dmi'
	icon_state = "glock"
	force = 10
	mag_type = /obj/item/ammo_box/magazine/glock9mm
	special_mags = TRUE
	mag_display = TRUE
	w_class = WEIGHT_CLASS_NORMAL
	can_suppress = FALSE
	fire_sound = list('modular_septic/sound/guns/glock1.ogg', 'modular_septic/sound/guns/glock2.ogg', 'modular_septic/sound/guns/glock3.ogg')
	rack_sound = 'sound/weapons/gun/pistol/rack.ogg'
	lock_back_sound = 'sound/weapons/gun/pistol/slide_lock.ogg'
	bolt_drop_sound = 'sound/weapons/gun/pistol/slide_drop.ogg'

/obj/item/gun/ballistic/automatic/pistol/glock/m10mm
	name = "\improper Gook-20"
	desc = "A reliable 10mm handgun with a large magazine capacity."
	icon = 'modular_septic/icons/obj/guns/pistol.dmi'
	icon_state = "glock_bbc"
	mag_type = /obj/item/ammo_box/magazine/glock10mm


code:
//Remove this dumb knockback shit pls
/obj/item/gun/ballistic/shotgun
	fire_sound = 'modular_septic/sound/guns/shotgun.ogg'
	pb_knockback = 0

////////////////////////////////
// SAIGA-12 AUTOMATIC SHOTGUN //
////////////////////////////////
/obj/item/gun/ballistic/shotgun/saiga
	name = "\improper REMIS-12 Automatic Shotgun"
	desc = "A gun that shoots. A lot."
	icon = 'modular_septic/icons/obj/guns/64x32.dmi'
	icon_state = "saiga"
	force = 14 //Extra Spicy!
	internal_magazine = FALSE
	mag_type = /obj/item/ammo_box/magazine/saigadrum
	weapon_weight = WEAPON_MEDIUM
	fire_sound = 'modular_septic/sound/guns/rape_gun1.ogg'
	mag_display = TRUE
	mag_display_ammo = FALSE
	empty_indicator = FALSE
	semi_auto = TRUE
	internal_magazine = FALSE


code:
/////////////////////////////////
// KRISS VECTOR SUB MACHINEGUN //
/////////////////////////////////
/obj/item/gun/ballistic/automatic/vector
	name = "\improper Chris Kektor SMG"
	desc = "An unconventional, ancient-designed sub-machine gun renowned for an accelerated rate of fire, reduced recoil and magazine size. Manufactured by ITOBE during the military revolution."
	icon = 'modular_septic/icons/obj/guns/smg.dmi'
	icon_state = "vector"
	pin = /obj/item/firing_pin
	force = 10
	mag_type = /obj/item/ammo_box/magazine/m45vector
	weapon_weight = WEAPON_MEDIUM
	fire_delay = 0.5
	burst_size = 4
	pickup_sound = 'modular_septic/sound/guns/vector_draw.ogg'
	mag_display = TRUE
	mag_display_ammo = FALSE
	empty_indicator = FALSE

////////////////////////////
// PAPA SHA SUBMACHINEGUN //
////////////////////////////
/obj/item/gun/ballistic/automatic/ppsh
	name = "\improper Papasha SMG"
	desc = "Despite the dated appearance the Papasha is more of a machine pistol than an smg, the unreliable drum magazine being discarded by the Death Sec Unit decades ago due to many mechanical faults."
	icon = 'modular_septic/icons/obj/guns/smg.dmi'
	icon_state = "ppsh"
	pin = /obj/item/firing_pin
	force = 10
	mag_type = /obj/item/ammo_box/magazine/ppsh9mm
	weapon_weight = WEAPON_MEDIUM
	fire_delay = 1.5
	burst_size = 3
	mag_display = TRUE
	mag_display_ammo = FALSE
	empty_indicator = FALSE


code:
/obj/item/gun
	//Surprisingly, this is a decently accurate amount for lots of guns
	carry_weight = 2.5
	pickup_sound = 'modular_septic/sound/guns/unholster.ogg'
	var/dry_fire_message = span_danger("*click*")
	var/safety_flags = SAFETY_FLAGS_DEFAULT
	var/safety_sound_volume = 60
	var/safety_sound = 'modular_septic/sound/weapons/safety1.ogg'

/obj/item/gun/update_overlays()
	. = ..()
	if(safety_flags & HAS_SAFETY)
		var/image/safety_overlay
		if((safety_flags & SAFETY_ENABLED) && (safety_flags & SAFETY_OVERLAY_ENABLED))
			safety_overlay = image(icon, src, "[initial(icon_state)]-safe")
		else if(!(safety_flags & SAFETY_ENABLED) && (safety_flags & SAFETY_OVERLAY_DISABLED))
			safety_overlay = image(icon, src, "[initial(icon_state)]-unsafe")
		if(safety_overlay)
			. += safety_overlay

/obj/item/gun/get_carry_weight()
	. = ..()
	if(istype(pin))
		. += pin.get_carry_weight()

/obj/item/gun/attack_self_secondary(mob/user, modifiers)
	. = ..()
	if(safety_flags & HAS_SAFETY)
		toggle_safety(user)

/obj/item/gun/examine(mob/user)
	. = ..()
	if(safety_flags & HAS_SAFETY)
		var/safety_text = span_red("OFF")
		if(safety_flags & SAFETY_ENABLED)
			safety_text = span_green("ON")
		. += "[p_their(TRUE)] safety is [safety_text]."

/obj/item/gun/can_trigger_gun(mob/living/user)
	. = ..()
	if(CHECK_MULTIPLE_BITFIELDS(safety_flags, HAS_SAFETY|SAFETY_ENABLED))
		return FALSE

/obj/item/gun/check_botched(mob/living/user, params)
	if(clumsy_check)
		if(istype(user))
			if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
				to_chat(user, span_userdanger("I shoot myself in the foot with [src]!"))
				var/shot_leg = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
				process_fire(user, user, FALSE, params, shot_leg)
				SEND_SIGNAL(user, COMSIG_MOB_CLUMSY_SHOOT_FOOT)
				user.dropItemToGround(src, TRUE)
				return TRUE

/obj/item/gun/shoot_with_empty_chamber(mob/living/user as mob|obj)
	if(ismob(user) && dry_fire_message)
		to_chat(user, dry_fire_message)
	if(dry_fire_sound)
		playsound(src, dry_fire_sound, 30, TRUE)

/obj/item/gun/proc/toggle_safety(mob/user)
	if(!(safety_flags & HAS_SAFETY))
		return
	playsound(src, safety_sound, safety_sound_volume, 0)
	if(safety_flags & SAFETY_ENABLED)
		safety_flags &= ~SAFETY_ENABLED
	else
		safety_flags |= SAFETY_ENABLED
	if(user)
		to_chat(user, span_notice("I [safety_flags & SAFETY_ENABLED ? "enable" : "disable"] [src]'s safety."))


code:
/obj/item/firing_pin
	carry_weight = 0.1

/obj/item/firing_pin/Initialize()
	. = ..()
	AddElement(/datum/element/multitool_emaggable)


code:
/obj/projectile
	/// Stat used in ranged combat
	var/stat_ranged = STAT_INTELLIGENCE
	/// Skill used in  ranged combat
	var/skill_ranged = SKILL_RANGED
	/// Specialty skill used in ranged combat
	var/specialty_ranged
	/// Chance to go through the mob and not embed
	var/mob_pierce_chance = 0
	/// Stored visible message
	var/hit_text = ""
	/// Stored target message
	var/target_hit_text = ""

/obj/projectile/prehit_pierce(atom/A)
	if((projectile_phasing & A.pass_flags_self) && (!phasing_ignore_direct_target || original != A))
		return PROJECTILE_PIERCE_PHASE
	if(projectile_piercing & A.pass_flags_self)
		return PROJECTILE_PIERCE_HIT
	if(ismovable(A))
		var/atom/movable/AM = A
		if(AM.throwing)
			return (projectile_phasing & LETPASSTHROW)? PROJECTILE_PIERCE_PHASE : ((projectile_piercing & LETPASSTHROW)? PROJECTILE_PIERCE_HIT : PROJECTILE_PIERCE_NONE)
	if(ismob(A) && prob(mob_pierce_chance))
		return PROJECTILE_PIERCE_HIT
	return PROJECTILE_PIERCE_NONE

/obj/projectile/process_hit(turf/T, atom/target, atom/bumped, hit_something = FALSE)
	// 1.
	if(QDELETED(src) || !T || !target)
		return
	// 2.
	impacted[target] = TRUE //hash lookup > in for performance in hit-checking
	// 3.
	var/mode = prehit_pierce(target)
	if(mode == PROJECTILE_DELETE_WITHOUT_HITTING)
		qdel(src)
		return hit_something
	else if(mode == PROJECTILE_PIERCE_PHASE)
		if(!(movement_type & PHASING))
			temporary_unstoppable_movement = TRUE
			movement_type |= PHASING
		return process_hit(T, select_target(T, target, bumped), bumped, hit_something) // try to hit something else
	// at this point we are going to hit the thing
	// in which case send signal to it
	SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args)
	if(mode == PROJECTILE_PIERCE_HIT)
		++pierces
	hit_something = TRUE
	var/result = target.bullet_act(src, def_zone, mode == PROJECTILE_PIERCE_HIT)
	if(result == BULLET_ACT_HIT)
		if(LAZYLEN(embedding))
			if(mode == PROJECTILE_PIERCE_HIT)
				SEND_SIGNAL(target, COMSIG_CARBON_ADD_TO_WOUND_MESSAGE, span_danger(" The [name] goes through!"))
				SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_WENT_THROUGH)
			else
				SEND_SIGNAL(target, COMSIG_CARBON_ADD_TO_WOUND_MESSAGE, span_danger(" The [name] embeds!"))
				SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_EMBEDDED)
	var/wound_message = ""
	if(iscarbon(target))
		var/mob/living/carbon/carbon_target = target
		wound_message = carbon_target.wound_message
	SEND_SIGNAL(target, COMSIG_CARBON_CLEAR_WOUND_MESSAGE)
	if(hit_text)
		target.visible_message("[hit_text][wound_message]", \
			self_message = "[target_hit_text][wound_message]", \
			blind_message = span_hear("I hear something piercing flesh!"), \
			vision_distance = COMBAT_MESSAGE_RANGE, \
			ignored_mobs = target)
	if(target_hit_text)
		to_chat(target, target_hit_text)
	SEND_SIGNAL(src, COMSIG_PROJECTILE_POSTHIT, firer, target, result, mode)
	if((result == BULLET_ACT_FORCE_PIERCE) || (mode == PROJECTILE_PIERCE_HIT))
		if(damage <= 0)
			return hit_something
		if(!(movement_type & PHASING))
			temporary_unstoppable_movement = TRUE
			movement_type |= PHASING
		return process_hit(T, select_target(T, target, bumped), bumped, TRUE)
	qdel(src)
	return hit_something

/obj/projectile/on_hit(atom/target, blocked = FALSE, pierce_hit)
	if(fired_from)
		SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
	var/obj/item/bodypart/hit_limb
	if(isliving(target))
		var/mob/living/living_target = target
		hit_limb = living_target.get_bodypart(living_target.check_limb_hit(def_zone))
	// i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
	// maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM
	SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb)
	if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason
		return
	var/turf/target_loca = get_turf(target)

	var/hitx
	var/hity
	if(target == original)
		hitx = target.pixel_x + p_x - 16
		hity = target.pixel_y + p_y - 16
	else
		hitx = target.pixel_x + rand(-8, 8)
		hity = target.pixel_y + rand(-8, 8)

	if(!nodamage && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75))
		var/turf/closed/wall/W = target_loca
		if(impact_effect_type && !hitscan)
			new impact_effect_type(target_loca, hitx, hity)

		W.add_dent(WALL_DENT_SHOT, hitx, hity)

		return BULLET_ACT_HIT

	if(!isliving(target))
		if(impact_effect_type && !hitscan)
			new impact_effect_type(target_loca, hitx, hity)
		if(isturf(target) && hitsound_wall)
			var/volume = clamp(vol_by_damage() + 20, 0, 100)
			if(suppressed)
				volume = 5
			playsound(loc, hitsound_wall, volume, TRUE, -1)
		return BULLET_ACT_HIT

	var/mob/living/living_target = target
	if(blocked != 100) // not completely blocked
		if(damage && (damage_type == BRUTE) && (sharpness & SHARP_EDGED|SHARP_POINTY) && living_target.blood_volume && (living_target.mob_biotypes & MOB_ORGANIC))
			var/splatter_dir = dir
			if(starting)
				splatter_dir = get_dir(starting, target_loca)
			if(isalien(living_target))
				new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
			else
				new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
			if(prob(33))
				living_target.add_splatter_floor(target_loca)
		if(impact_effect_type && !hitscan)
			new impact_effect_type(target_loca, hitx, hity)

		var/organ_hit_text = ""
		var/zone_hit = hit_limb?.body_zone
		if(zone_hit)
			organ_hit_text = " in \the [parse_zone(zone_hit)]"
		if(suppressed == SUPPRESSED_VERY)
			playsound(loc, hitsound, 5, TRUE, -1)
		else if(suppressed)
			playsound(loc, hitsound, 5, TRUE, -1)
			target_hit_text = span_userdanger("I'm hit by \the [src][organ_hit_text]!")
		else
			if(hitsound)
				var/volume = vol_by_damage()
				playsound(src, hitsound, volume, TRUE, -1)
			hit_text = span_danger("[living_target] is hit by \the [src][organ_hit_text]!")
			target_hit_text = span_userdanger("I'm hit by \the [src][organ_hit_text]!")
		living_target.on_hit(src)

	var/reagent_note
	if(reagents?.reagent_list)
		reagent_note = " REAGENTS:"
		for(var/datum/reagent/R in reagents.reagent_list)
			reagent_note += "[R.name] ([num2text(R.volume)])"

	if(ismob(firer))
		log_combat(firer, living_target, "shot", src, reagent_note)
	else
		living_target.log_message("has been shot by [firer] with [src]", LOG_ATTACK, color="orange")

	return BULLET_ACT_HIT


code:
/obj/projectile/bullet
	mob_pierce_chance = 35
	embedding = list(embed_chance=100, fall_chance=0, jostle_chance=0, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.5, pain_mult=3, rip_time=20)
	wound_falloff_tile = 0
	embed_falloff_tile = 0
	sharpness = SHARP_POINTY


code:
/obj/projectile/bullet/shotgun_slug
	name = "12g shotgun slug"
	damage = 60
	wound_bonus = 0
	sharpness = SHARP_POINTY

/obj/projectile/bullet/pellet/shotgun_buckshot
	name = "buckshot pellet"
	damage = 10
	wound_bonus = 5
	bare_wound_bonus = 5
	wound_falloff_tile = 0
	sharpness = SHARP_POINTY


code:
/datum/reagents/add_reagent_list(list/list_reagents, list/data, no_react = FALSE)
	for(var/r_id in list_reagents)
		var/amt = list_reagents[r_id]
		add_reagent(r_id, amt, data, no_react = no_react)

/datum/reagents/copy_to(obj/target, amount=1, multiplier=1, preserve_data=1, no_react=0)
	var/list/cached_reagents = reagent_list
	if(!target || !total_volume)
		return

	var/datum/reagents/R
	if(istype(target, /datum/reagents))
		R = target
	else
		if(!target.reagents)
			return
		R = target.reagents

	if(amount < 0)
		return

	amount = min(min(amount, total_volume), R.maximum_volume-R.total_volume)
	var/part = amount / total_volume
	var/trans_data = null
	for(var/datum/reagent/reagent as anything in cached_reagents)
		var/copy_amount = reagent.volume * part
		if(preserve_data)
			trans_data = reagent.data
		R.add_reagent(reagent.type, copy_amount * multiplier, trans_data, added_purity = reagent.purity, added_ph = reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT)

	//pass over previous ongoing reactions before handle_reactions is called
	transfer_reactions(R)

	src.update_total()
	R.update_total()
	if(!no_react)
		R.handle_reactions()
		src.handle_reactions()
	return amount

/datum/reagents/proc/get_total_accelerant_quality()
	var/quality = 0
	for(var/i in reagent_list)
		var/datum/reagent/reagent = i
		quality += reagent.volume * reagent.accelerant_quality
	return quality


code:
/datum/reagent
	///Does this evaporate as a puddle?
	var/liquid_evaporation_rate = 0
	///How much fire power does the liquid have, for burning on simulated liquids. Not enough fire power/unit of entire mixture may result in no fire
	var/liquid_fire_power = 0
	///How fast does the liquid burn on simulated turfs, if it does
	var/liquid_fire_burnrate = 0
	///Whether a fire from this requires oxygen in the atmosphere
	var/fire_needs_oxygen = TRUE
	///Initial method used on a mob
	var/initial_methods = INJECT
	///Organic reagents can't get processed by IPCs, etc
	var/process_flags = REAGENT_ORGANIC
	///Dictates what chemicals we can separate from
	var/polarity = POLARITY_POLAR
	///How much of a solid reagent of the same polarity we can dissolve per 1u
	var/dissolve_per_unit = 1
	///How good of an accelerant is this reagent - used for turf fires
	var/accelerant_quality = 0

/datum/reagent/expose_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection)
	initial_methods = methods
	return ..()


code:
/datum/reagent/consumable/ethanol
	//Alcohol evaporates rather fast - 20u every 10 seconds
	liquid_evaporation_rate = 10
	liquid_fire_power = 10
	liquid_fire_burnrate = 0.1
	accelerant_quality = 10

/datum/reagent/consumable/ethanol/on_mob_metabolize(mob/living/L)
	. = ..()
	L.increase_chem_effect(CE_PAINKILLER, boozepwr/5, "alcohol")

/datum/reagent/consumable/ethanol/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.decrease_chem_effect(CE_PAINKILLER, boozepwr/5, "alcohol")

/datum/reagent/consumable/ethanol/beer
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/beer/light
	liquid_fire_power = 0

/datum/reagent/consumable/ethanol/beer/maltliquor
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/beer/green
	liquid_fire_power = 1

/datum/reagent/consumable/ethanol/kahlua
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/whiskey
	liquid_fire_power = 8

/datum/reagent/consumable/ethanol/whiskey/kong
	liquid_fire_power = 10

/datum/reagent/consumable/ethanol/whiskey/candycorn
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/thirteenloko
	liquid_fire_power = 10

/datum/reagent/consumable/ethanol/vodka
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/bilk
	liquid_fire_power = 0

/datum/reagent/consumable/ethanol/threemileisland
	liquid_fire_power = 1

/datum/reagent/consumable/ethanol/gin
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/rum
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/tequila
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/vermouth
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/wine
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/lizardwine
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/grappa
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/amaretto
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/cognac
	liquid_fire_power = 8

/datum/reagent/consumable/ethanol/absinthe
	liquid_fire_power = 8

/datum/reagent/consumable/ethanol/hooch
	liquid_fire_power = 10

/datum/reagent/consumable/ethanol/ale
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/goldschlager
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/patron
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/gintonic
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/rum_coke
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/cuba_libre
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/whiskey_cola
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/martini
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/vodkamartini
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/white_russian
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/screwdrivercocktail
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/booger
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/bloody_mary
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/brave_bull
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/tequila_sunrise
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/toxins_special
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/beepsky_smash
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/irish_cream
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/manly_dorf
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/longislandicedtea
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/moonshine
	liquid_fire_power = 10

/datum/reagent/consumable/ethanol/b52
	liquid_fire_power = 9

/datum/reagent/consumable/ethanol/irishcoffee
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/margarita
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/black_russian
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/manhattan
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/manhattan_proj
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/whiskeysoda
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/antifreeze
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/barefoot
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/snowwhite
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/demonsblood
	liquid_fire_power = 8

/datum/reagent/consumable/ethanol/devilskiss
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/vodkatonic
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/ginfizz
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/bahama_mama
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/singulo
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/sbiten
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/red_mead
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/mead
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/iced_beer
	liquid_fire_power = 1

/datum/reagent/consumable/ethanol/grog
	liquid_fire_power = 0

/datum/reagent/consumable/ethanol/aloe
	liquid_fire_burnrate = 4

/datum/reagent/consumable/ethanol/andalusia
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/alliescocktail
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/acid_spit
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/amasec
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/changelingsting
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/irishcarbomb
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/syndicatebomb
	liquid_fire_power = 9

/datum/reagent/consumable/ethanol/hiveminderaser
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/erikasurprise
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/driestmartini
	liquid_fire_power = 8

/datum/reagent/consumable/ethanol/bananahonk
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/silencer
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/drunkenblumpkin
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/whiskey_sour
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/hcider
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/fetching_fizz
	liquid_fire_power = 1

/datum/reagent/consumable/ethanol/hearty_punch
	liquid_fire_power = 9

/datum/reagent/consumable/ethanol/bacchus_blessing
	liquid_fire_power = 30

/datum/reagent/consumable/ethanol/atomicbomb
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/gargle_blaster
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/neurotoxin
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/hippies_delight
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/eggnog
	liquid_fire_power = 0

/datum/reagent/consumable/ethanol/narsour
	liquid_fire_power = 1

/datum/reagent/consumable/ethanol/triple_sec
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/creme_de_menthe
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/creme_de_cacao
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/creme_de_coconut
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/quadruple_sec
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/quintuple_sec
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/grasshopper
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/stinger
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/bastion_bourbon
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/squirt_cider
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/fringe_weaver
	liquid_fire_power = 9

/datum/reagent/consumable/ethanol/sugar_rush
	liquid_fire_power = 1

/datum/reagent/consumable/ethanol/crevice_spike
	liquid_fire_power = -1

/datum/reagent/consumable/ethanol/sake
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/peppermint_patty
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/alexander
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/amaretto_alexander
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/sidecar
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/between_the_sheets
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/kamikaze
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/mojito
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/moscow_mule
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/fernet
	liquid_fire_power = 10

/datum/reagent/consumable/ethanol/fernet_cola
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/fanciulli
	liquid_fire_power = -1

/datum/reagent/consumable/ethanol/branca_menta
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/blank_paper
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/fruit_wine
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/champagne
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/wizz_fizz
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/bug_spray
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/applejack
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/jack_rose
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/turbo
	liquid_fire_power = 9

/datum/reagent/consumable/ethanol/old_timer
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/rubberneck
	liquid_fire_power = 6

/datum/reagent/consumable/ethanol/duplex
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/trappist
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/blazaam
	liquid_fire_power = 7

/datum/reagent/consumable/ethanol/planet_cracker
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/mauna_loa
	liquid_fire_power = 4

/datum/reagent/consumable/ethanol/painkiller
	liquid_fire_power = 2

/datum/reagent/consumable/ethanol/pina_colada
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/pruno
	liquid_fire_power = 9

/datum/reagent/consumable/ethanol/ginger_amaretto
	liquid_fire_power = 3

/datum/reagent/consumable/ethanol/godfather
	liquid_fire_power = 5

/datum/reagent/consumable/ethanol/godmother
	liquid_fire_power = 5


code:
//BLOOD
/datum/reagent/blood/expose_atom(atom/exposed_atom, reac_volume)
	. = ..()
	if(!.)
		exposed_atom.adjust_germ_level(GERM_PER_UNIT_BLOOD * reac_volume)

//PISS
/datum/reagent/consumable/piss
	data = list("viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
	name = "Urine"
	description = "Liquid human waste. Disgusting."
	taste_description = "bad beer"
	reagent_state = LIQUID
	color = COLOR_YELLOW_PISS
	liquid_evaporation_rate = 7
	ph = 7.5

/datum/reagent/consumable/piss/expose_atom(atom/exposed_atom, reac_volume)
	. = ..()
	if(!.)
		exposed_atom.adjust_germ_level(GERM_PER_UNIT_PISS * reac_volume)
//SHIT
/datum/reagent/consumable/shit
	data = list("viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
	name = "Excrement"
	description = "Solid human waste. Disgusting."
	taste_description = "literal shit"
	reagent_state = SOLID
	color = COLOR_BROWN_SHIT
	liquid_evaporation_rate = 3
	ph = 6.6

/datum/reagent/consumable/shit/on_new(list/data)
	if(istype(data))
		SetViruses(src, data)

/datum/reagent/consumable/shit/expose_atom(atom/exposed_atom, reac_volume)
	. = ..()
	if(!.)
		exposed_atom.adjust_germ_level(GERM_PER_UNIT_SHIT * reac_volume)

/datum/reagent/consumable/shit/expose_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection)
	. = ..()
	if(data && data["viruses"])
		for(var/thing in data["viruses"])
			var/datum/disease/strain = thing
			if((strain.spread_flags & DISEASE_SPREAD_SPECIAL) || (strain.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS))
				continue
			if((methods & (TOUCH|VAPOR)) && (strain.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS))
				exposed_mob.ContactContractDisease(strain)
			else //ingest, patch or inject
				exposed_mob.ForceContractDisease(strain)

/datum/reagent/consumable/shit/expose_turf(turf/exposed_turf, reac_volume)//splash the blood all over the place
	. = ..()
	if(!istype(exposed_turf))
		return
	if(reac_volume < 3)
		return

	var/obj/effect/decal/cleanable/blood/shitsplatter
	for(var/obj/effect/decal/cleanable/blood/splatter in exposed_turf)
		if(splatter.blood_state == BLOOD_STATE_SHIT)
			shitsplatter = splatter
	if(!shitsplatter)
		shitsplatter = new /obj/effect/decal/cleanable/blood/shit(exposed_turf)
	if(data["blood_DNA"])
		shitsplatter.add_shit_DNA(list(data["blood_DNA"] = data["blood_type"]))

//CUM
/datum/reagent/consumable/cum
	data = list("viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
	name = "Semen"
	description = "Baby batter..."
	taste_description = "something salty"
	reagent_state = LIQUID
	color = COLOR_WHITE_CUM
	liquid_evaporation_rate = 3
	ph = 7.2

/datum/reagent/consumable/cum/on_new(list/data)
	if(istype(data))
		SetViruses(src, data)

/datum/reagent/consumable/cum/expose_atom(atom/exposed_atom, reac_volume)
	. = ..()
	if(!.)
		exposed_atom.adjust_germ_level(GERM_PER_UNIT_SHIT * reac_volume)

/datum/reagent/consumable/cum/expose_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection)
	. = ..()
	if(data && data["viruses"])
		for(var/thing in data["viruses"])
			var/datum/disease/strain = thing
			if((strain.spread_flags & DISEASE_SPREAD_SPECIAL) || (strain.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS))
				continue
			if((methods & (TOUCH|VAPOR)) && (strain.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS))
				exposed_mob.ContactContractDisease(strain)
			else //ingest, patch or inject
				exposed_mob.ForceContractDisease(strain)

/datum/reagent/consumable/cum/expose_turf(turf/exposed_turf, reac_volume)//splash the blood all over the place
	. = ..()
	if(!istype(exposed_turf))
		return
	if(reac_volume < 3)
		return

	var/obj/effect/decal/cleanable/blood/cumsplatter
	for(var/obj/effect/decal/cleanable/blood/splatter in exposed_turf)
		if(splatter.blood_state == BLOOD_STATE_CUM)
			cumsplatter = splatter
	if(!cumsplatter)
		cumsplatter = new /obj/effect/decal/cleanable/blood/cum(exposed_turf)
	if(data["blood_DNA"])
		cumsplatter.add_shit_DNA(list(data["blood_DNA"] = data["blood_type"]))

//FEMCUM
/datum/reagent/consumable/femcum
	data = list("viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
	name = "Squirt"
	description = "I can't believe it's not urine!"
	taste_description = "something slightly sweet"
	taste_mult = 0.5
	reagent_state = LIQUID
	color = COLOR_WHITE_FEMCUM
	liquid_evaporation_rate = 3
	ph = 7.2

/datum/reagent/consumable/femcum/on_new(list/data)
	if(istype(data))
		SetViruses(src, data)

/datum/reagent/consumable/femcum/expose_atom(atom/exposed_atom, reac_volume)
	. = ..()
	if(!.)
		exposed_atom.adjust_germ_level(GERM_PER_UNIT_SHIT * reac_volume)

/datum/reagent/consumable/femcum/expose_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection)
	. = ..()
	if(data && data["viruses"])
		for(var/thing in data["viruses"])
			var/datum/disease/strain = thing
			if((strain.spread_flags & DISEASE_SPREAD_SPECIAL) || (strain.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS))
				continue
			if((methods & (TOUCH|VAPOR)) && (strain.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS))
				exposed_mob.ContactContractDisease(strain)
			else //ingest, patch or inject
				exposed_mob.ForceContractDisease(strain)

/datum/reagent/consumable/femcum/expose_turf(turf/exposed_turf, reac_volume)//splash the blood all over the place
	. = ..()
	if(!istype(exposed_turf))
		return
	if(reac_volume < 3)
		return

	var/obj/effect/decal/cleanable/blood/femcumsplatter
	for(var/obj/effect/decal/cleanable/blood/splatter in exposed_turf)
		if(splatter.blood_state == BLOOD_STATE_FEMCUM)
			femcumsplatter = splatter
	if(!femcumsplatter)
		femcumsplatter = new /obj/effect/decal/cleanable/blood/femcum(exposed_turf)
	if(data["blood_DNA"])
		femcumsplatter.add_shit_DNA(list(data["blood_DNA"] = data["blood_type"]))


code:
//DEV NOTE: Implementing inverse and failed chems is a waste of time i do not want to invest on right now
//Have somewhat boring chems for now

/******BRUTE******/
/*(Common) Suffix: -ine*/

//libital -> codeine
/datum/reagent/medicine/c2/libital
	name = "Codeine"
	description = "Codeine is a slight painkiller and brute reliever opioid."
	reagent_state = SOLID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	addiction_types = list(/datum/addiction/opiods = 5) //1 per 2 seconds
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/libital/on_mob_metabolize(mob/living/L)
	. = ..()
	L.add_chem_effect(CE_PAINKILLER, 20, "codeine")

/datum/reagent/medicine/c2/libital/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_PAINKILLER, "codeine")

/datum/reagent/medicine/c2/libital/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	M.heal_overall_damage(brute = -2 * REM * delta_time)
	return TRUE

//helbital -> bicaridine
/datum/reagent/medicine/c2/helbital
	name = "Bicaridine"
	description = "Bicaridine is a fast-acting medication to treat physical trauma. \
				Overdosing may cause headaches and dizzyness."
	reagent_state = LIQUID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	overdose_threshold = OVERDOSE_STANDARD
	addiction_types = list(/datum/addiction/opiods = 10) //2 per 2 seconds
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/helbital/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	M.heal_overall_damage(brute = 6 * REM * delta_time)
	return TRUE

/datum/reagent/medicine/c2/helbital/overdose_start(mob/living/M)
	. = ..()
	M.HeadRape(4 SECONDS)

/datum/reagent/medicine/c2/helbital/overdose_process(mob/living/M, delta_time, times_fired)
	. = ..()
	M.HeadRape(4 SECONDS)

/******BURN******/
/*(Common) Suffix: -ane*/

//aiuri -> kelotane
/datum/reagent/medicine/c2/aiuri
	name = "Kelotane"
	description = "Kelotane is a fast-acting medication to treat burns. \
				Overdosing causes toxic allergic reactions."
	reagent_state = LIQUID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	overdose_threshold = OVERDOSE_STANDARD
	addiction_types = list(/datum/addiction/opiods = 10) //2 per 2 seconds
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/aiuri/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	M.heal_overall_damage(burn = -6 * REM * delta_time)
	return TRUE

/datum/reagent/medicine/c2/aiuri/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_TOXIN, "kelotane")

/datum/reagent/medicine/c2/aiuri/overdose_start(mob/living/M)
	. = ..()
	M.add_chem_effect(CE_TOXIN, 1, "kelotane")

/datum/reagent/medicine/c2/aiuri/overdose_process(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	M.adjustToxLoss(2 * REM * delta_time)

//lenturi -> naproxane
/datum/reagent/medicine/c2/lenturi
	name = "Naproxane"
	description = "Naproxane is an anti-inflammatory and burn reliever opioid."
	reagent_state = LIQUID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	addiction_types = list(/datum/addiction/opiods = 5) //1 per 2 seconds
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/lenturi/on_mob_metabolize(mob/living/L)
	. = ..()
	L.add_chem_effect(CE_PAINKILLER, 20, "naproxen")

/datum/reagent/medicine/c2/lenturi/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_PAINKILLER, "naproxen")

/datum/reagent/medicine/c2/lenturi/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	M.heal_overall_damage(burn = -2 * REM * delta_time)
	return TRUE

/******TOXIN******/
/*(Common) Suffix: -al*/

//multiver -> charcoal
/datum/reagent/medicine/c2/multiver
	name = "Activated Charcoal"
	description = "Activated charcoal is a broad spectrum antitoxin used to stimulate toxin filtering by the kidneys and liver."
	reagent_state = SOLID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/multiver/on_mob_metabolize(mob/living/L)
	. = ..()
	L.add_chem_effect(CE_ANTITOX, 65, "charcoal")

/datum/reagent/medicine/c2/multiver/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_ANTITOX, "charcoal")

//syriniver -> dylovene
/datum/reagent/medicine/c2/syriniver
	name = "Dylovenal"
	description = "Dylovenal is a broad-spectrum antitoxin used to neutralize toxins in the bloodstream."

/datum/reagent/medicine/c2/syriniver/on_mob_metabolize(mob/living/L)
	. = ..()
	L.add_chem_effect(CE_ANTITOX, 20, "dylovenal")

/datum/reagent/medicine/c2/syriniver/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_ANTITOX, 20, "dylovenal")

/datum/reagent/medicine/c2/syriniver/on_mob_life(mob/living/carbon/human/M, delta_time, times_fired)
	. = ..()
	M.adjustToxLoss(-3 * REM * delta_time)
	return TRUE

/******OXY******/
/*(Common) Suffix: -ol*/

//convermol -> formoterol
/datum/reagent/medicine/c2/convermol
	name = "Formoterol"
	description = "A bronchodilator capable of efficiently oxygenating the lungs of affected patients."
	reagent_state = SOLID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	overdose_threshold = 0
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/convermol/on_mob_life(mob/living/carbon/human/M, delta_time, times_fired)
	. = ..()
	M.adjustOxyLoss(-4 * REM * delta_time)
	return TRUE

//tirimol -> levalbuterol
/datum/reagent/medicine/c2/tirimol
	name = "Levalbuterol"
	description = "Levalbuterol is a beta agonist capable of efficiently restoring blood oxygenation."
	reagent_state = LIQUID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	overdose_threshold = 0
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/tirimol/on_mob_metabolize(mob/living/L)
	. = ..()
	L.add_chem_effect(CE_OXYGENATED, 1, "levalbuterol")

/datum/reagent/medicine/c2/tirimol/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_OXYGENATED, "levalbuterol")

/******COMBOS******/
/*(Common) Suffix: -eral*/

//synthflesh -> menderal
/datum/reagent/medicine/c2/synthflesh
	name = "Menderal" //Because it mends all... I am so clever!
	description = "Heals both brute and burn damage. 100u or more can restore corpses husked by burns. \
				Toxic when ingested, touch application only."

/datum/reagent/medicine/c2/synthflesh/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE)
	. = ..()
	if(exposed_mob.stat == DEAD)
		show_message = 0
	if(!(initial_methods & (PATCH|TOUCH|VAPOR)))
		return
	var/mob/living/carbon/carbies = exposed_mob
	if(istype(carbies))
		for(var/i in carbies.all_wounds)
			var/datum/wound/iter_wound = i
			iter_wound.on_synthflesh(reac_volume)
	exposed_mob.heal_overall_damage(brute = -1.5 * reac_volume, burn = -1.5 * reac_volume)
	if(show_message)
		to_chat(exposed_mob, span_danger("You feel your burns and bruises healing! It stings like hell!"))
	SEND_SIGNAL(exposed_mob, COMSIG_ADD_MOOD_EVENT, "painful_medicine", /datum/mood_event/painful_medicine)
	if(HAS_TRAIT_FROM(exposed_mob, TRAIT_HUSK, BURN) && carbies.getFireLoss() < UNHUSK_DAMAGE_THRESHOLD && (carbies.reagents.get_reagent_amount(/datum/reagent/medicine/c2/synthflesh) + reac_volume >= SYNTHFLESH_UNHUSK_AMOUNT))
		carbies.cure_husk(BURN)
		carbies.visible_message("<span class='nicegreen'>A rubbery liquid coats [carbies]'s burns. [carbies] looks a lot healthier!") //we're avoiding using the phrases "burnt flesh" and "burnt skin" here because carbies could be a skeleton or a golem or something

/datum/reagent/medicine/c2/synthflesh/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	if(!(initial_methods & (PATCH|TOUCH|VAPOR)))
		M.adjustToxLoss(2 * REM * delta_time)
	return TRUE

//probital -> dicorderal
/datum/reagent/medicine/c2/probital
	name = "Dicorderal"
	description = "Heals both brute and burn damage, but rather slowly."
	reagent_state = LIQUID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	metabolization_rate = REAGENTS_METABOLISM
	overdose_threshold = 0
	failed_chem = null
	inverse_chem = null
	impure_chem = null

/datum/reagent/medicine/c2/probital/on_mob_metabolize(mob/living/L)
	. = ..()
	L.add_chem_effect(CE_PAINKILLER, 25, "dicorderal")

/datum/reagent/medicine/c2/probital/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_PAINKILLER, "dicorderal")

/datum/reagent/medicine/c2/probital/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	M.heal_overall_damage(brute = 3 * REM * delta_time, burn = 3 * REM * delta_time)
	return TRUE

/******ORGAN HEALING******/
/*Suffix: -il*/
/datum/reagent/medicine/c2/penthrite
	name = "Minoxidil"
	description = "Minoxidil is used as a blood pressure reducer, as well as healing organ damage over time."
	color = "#fff48c8e"
	ph = 3.8
	reagent_state = LIQUID
	chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
	inverse_chem = null

/datum/reagent/medicine/c2/penthrite/on_mob_metabolize(mob/living/L)
	. = ..()
	L.add_chem_effect(CE_PULSE, -1, "minoxidil")

/datum/reagent/medicine/c2/penthrite/on_mob_end_metabolize(mob/living/L)
	. = ..()
	L.remove_chem_effect(CE_PULSE, "minoxidil")

/datum/reagent/medicine/c2/penthrite/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	. = ..()
	var/list/damaged_organs = list()
	for(var/obj/item/organ/O in M.internal_organs)
		if(O.damage && !(O.organ_flags & ORGAN_NO_VIOLENT_DAMAGE))
			damaged_organs += O
	if(length(damaged_organs))
		var/obj/item/organ/organ = pick(damaged_organs)
		organ.applyOrganDamage(-2 * 0.5 * delta_time)
	return TRUE


code:
/datum/reagents/proc/replace_reagent(replaced_reagent, replacement)
	var/amount = get_reagent_amount(replaced_reagent)
	if(amount)
		del_reagent(replaced_reagent)
		add_reagent(replacement, amount)


code:
/datum/reagent/consumable
	/// How much hydration this reagent supplies
	var/hydration_factor = 1 * REAGENTS_METABOLISM

/datum/reagent/consumable/on_mob_life(mob/living/carbon/M, delta_time, times_fired)
	if(ishuman(M))
		var/mob/living/carbon/human/H = M
		if(!HAS_TRAIT(H, TRAIT_NOHUNGER))
			var/stomach_efficiency = H.getorganslotefficiency(ORGAN_SLOT_STOMACH)
			if(stomach_efficiency >= ORGAN_FAILING_EFFICIENCY)
				H.adjust_nutrition(nutriment_factor * REM * delta_time)
		if(!HAS_TRAIT(H, TRAIT_NOTHIRST))
			var/kidney_efficiency = H.getorganslotefficiency(ORGAN_SLOT_KIDNEYS)
			if(kidney_efficiency >= ORGAN_FAILING_EFFICIENCY)
				H.adjust_hydration(hydration_factor * REM * delta_time)
	current_cycle++
	if(length(reagent_removal_skip_list))
		return
	holder.remove_reagent(type, metabolization_rate * delta_time)