code:
/datum/award/achievement/boss
	category = "Bosses"
	icon = "baseboss"

/datum/award/achievement/boss/tendril_exterminator
	name = "Tendril Exterminator"
	desc = "Watch your step"
	database_id = BOSS_MEDAL_TENDRIL
	icon = "tendril"

/datum/award/achievement/boss/boss_killer
	name = "Boss Killer"
	desc = "You've come a long ways from asking how to switch hands."
	database_id = "Boss Killer"
	icon = "firstboss"

/datum/award/achievement/boss/blood_miner_kill
	name = "Blood-Drunk Miner Killer"
	desc = "I guess he couldn't handle his drink that well."
	database_id = BOSS_MEDAL_MINER
	icon = "miner"

/datum/award/achievement/boss/demonic_miner_kill
	name = "Demonic-Frost Miner Killer"
	desc = "Definitely harder than the Blood-Drunk Miner."
	database_id = BOSS_MEDAL_FROSTMINER

/datum/award/achievement/boss/bubblegum_kill
	name = "Bubblegum Killer"
	desc = "I guess he wasn't made of candy after all"
	database_id = BOSS_MEDAL_BUBBLEGUM
	icon = "bbgum"

/datum/award/achievement/boss/colossus_kill
	name = "Colossus Killer"
	desc = "The bigger they are... the better the loot"
	database_id = BOSS_MEDAL_COLOSSUS
	icon = "colossus"

/datum/award/achievement/boss/drake_kill
	name = "Drake Killer"
	desc = "Now I can wear Rune Platebodies!"
	database_id = BOSS_MEDAL_DRAKE
	icon = "drake"

/datum/award/achievement/boss/hierophant_kill
	name = "Hierophant Killer"
	desc = "Hierophant, but not triumphant."
	database_id = BOSS_MEDAL_HIEROPHANT
	icon = "hierophant"

/datum/award/achievement/boss/legion_kill
	name = "Legion Killer"
	desc = "We were many..now we are none."
	database_id = BOSS_MEDAL_LEGION
	icon = "legion"

/datum/award/achievement/boss/swarmer_beacon_kill
	name = "Swarm Beacon Killer"
	desc = "GET THEM OFF OF ME!"
	database_id = BOSS_MEDAL_SWARMERS
	icon = "swarmer"

/datum/award/achievement/boss/wendigo_kill
	name = "Wendigo Killer"
	desc = "You've now ruined years of mythical storytelling."
	database_id = BOSS_MEDAL_WENDIGO

/datum/award/achievement/boss/blood_miner_crusher
	name = "Blood-Drunk Miner Crusher"
	desc = "I guess he couldn't handle his drink that well."
	database_id = BOSS_MEDAL_MINER_CRUSHER
	icon = "miner"

/datum/award/achievement/boss/demonic_miner_crusher
	name = "Demonic-Frost Miner Crusher"
	desc = "Definitely harder than the Blood-Drunk Miner."
	database_id = BOSS_MEDAL_FROSTMINER_CRUSHER

/datum/award/achievement/boss/bubblegum_crusher
	name = "Bubblegum Crusher"
	desc = "I guess he wasn't made of candy after all"
	database_id = BOSS_MEDAL_BUBBLEGUM_CRUSHER
	icon = "bbgum"

/datum/award/achievement/boss/colossus_crusher
	name = "Colossus Crusher"
	desc = "The bigger they are... the better the loot"
	database_id = BOSS_MEDAL_COLOSSUS_CRUSHER
	icon = "colossus"

/datum/award/achievement/boss/drake_crusher
	name = "Drake Crusher"
	desc = "Now I can wear Rune Platebodies!"
	database_id = BOSS_MEDAL_DRAKE_CRUSHER
	icon = "drake"

/datum/award/achievement/boss/hierophant_crusher
	name = "Hierophant Crusher"
	desc = "Hierophant, but not triumphant."
	database_id = BOSS_MEDAL_HIEROPHANT_CRUSHER
	icon = "hierophant"

/datum/award/achievement/boss/legion_crusher
	name = "Legion Crusher"
	desc = "We were many... now we are none."
	database_id = BOSS_MEDAL_LEGION_CRUSHER

/datum/award/achievement/boss/swarmer_beacon_crusher
	name = "Swarm Beacon Crusher"
	desc = "GET THEM OFF OF ME!"
	database_id = BOSS_MEDAL_SWARMERS_CRUSHER

/datum/award/achievement/boss/wendigo_crusher
	name = "Wendigo Crusher"
	desc = "You've now ruined years of mythical storytelling."
	database_id = BOSS_MEDAL_WENDIGO_CRUSHER

//should be removed soon
/datum/award/achievement/boss/king_goat_kill
	name = "King Goat Killer"
	desc = "The king is dead, long live the king!"
	database_id = BOSS_MEDAL_KINGGOAT
	icon = "goatboss"

/datum/award/achievement/boss/king_goat_crusher
	name = "King Goat Crusher"
	desc = "The king is dead, long live the king!"
	database_id = BOSS_MEDAL_KINGGOAT_CRUSHER
	icon = "goatboss"


code:
/datum/award/score/tendril_score
	name = "Tendril Score"
	desc = "Watch your step"
	database_id = TENDRIL_CLEAR_SCORE

/datum/award/score/boss_score
	name = "Bosses Killed"
	desc = "You've killed HOW many?"
	database_id = BOSS_SCORE

/datum/award/score/blood_miner_score
	name = "Blood-Drunk Miners Killed"
	desc = "You've killed HOW many?"
	database_id = MINER_SCORE

/datum/award/score/demonic_miner_score
	name = "Demonic-Frost Miners Killed"
	desc = "You've killed HOW many?"
	database_id = FROST_MINER_SCORE

/datum/award/score/bubblegum_score
	name = "Bubblegums Killed"
	desc = "You've killed HOW many?"
	database_id = BUBBLEGUM_SCORE

/datum/award/score/colussus_score
	name = "Colossus Killed"
	desc = "You've killed HOW many?"
	database_id = COLOSSUS_SCORE

/datum/award/score/drake_score
	name = "Drakes Killed"
	desc = "You've killed HOW many?"
	database_id = DRAKE_SCORE

/datum/award/score/hierophant_score
	name = "Hierophants Killed"
	desc = "You've killed HOW many?"
	database_id = HIEROPHANT_SCORE

/datum/award/score/legion_score
	name = "Legions Killed"
	desc = "You've killed HOW many?"
	database_id = LEGION_SCORE

/datum/award/score/swarmer_beacon_score
	name = "Swarmer Beacons Killed"
	desc = "You've killed HOW many?"
	database_id = SWARMER_BEACON_SCORE

/datum/award/score/wendigo_score
	name = "Wendigos Killed"
	desc = "You've killed HOW many?"
	database_id = WENDIGO_SCORE

repo_name: AnalWerewolf/sundown

license: mit


code:
/datum/award/achievement/jobs
	category = "Jobs"
	icon = "basemisc"

//chemistry

/datum/award/achievement/jobs/chemistry_tut
	name = "Perfect chemistry blossom"
	desc = "Passed the chemistry tutorial with perfect purity!"
	database_id = MEDAL_CHEM_TUT
	icon = "chem_tut"

//all of service! hip hip!

/datum/award/achievement/jobs/service_bad
	name = "Centcom Grade: Shitty Service"
	desc = "Well, you at least tried. How about trying harder?"
	database_id = MEDAL_BAD_SERVICE
	icon = "chem_tut"

/datum/award/achievement/jobs/service_okay
	name = "Centcom Grade: Acceptable Service"
	desc = "Well, it'll do! You and your department did just fine."
	database_id = MEDAL_OKAY_SERVICE
	icon = "chem_tut"

/datum/award/achievement/jobs/service_good
	name = "Centcom Grade: Exemplary Service"
	desc = "Centcom is very impressed with your department!"
	database_id = MEDAL_GOOD_SERVICE
	icon = "chem_tut"

//civilian achievies! while not recognized by the code, it is recognized by our hearts


code:
//chef

/datum/award/score/chef_tourist_score
	name = "Tourists Served as Chef Highscore"
	desc = "Your highscore on serving tourist bots as chef."
	database_id = CHEF_TOURISTS_SERVED

//bartender

/datum/award/score/bartender_tourist_score
	name = "Tourists Served as Bartender Highscore"
	desc = "Your highscore on serving tourist bots as bartender."
	database_id = BARTENDER_TOURISTS_SERVED


code:
/datum/award/achievement/mafia
	category = "Mafia"
	icon = "basemafia"

///ALL THE ACHIEVEMENTS FOR WINNING A ROUND AS A ROLE///

/datum/award/achievement/mafia/assistant
	name = "Assistant Victory"
	desc = "If you got killed instead of someone more important, you just flexed the true strength of your \"\"\"\"role\"\"\"\"."
	database_id = MAFIA_MEDAL_ASSISTANT
	icon = "town"

/datum/award/achievement/mafia/detective
	name = "Detective Victory"
	desc = "If you did this with a Medical Doctor in the game, i'm not really that impressed."
	database_id = MAFIA_MEDAL_DETECTIVE
	icon = "town"

/datum/award/achievement/mafia/psychologist
	name = "Psychologist Victory"
	desc = "You learned how to not reveal someone random night one! Or... maybe you're just a lucky bastard."
	database_id = MAFIA_MEDAL_PSYCHOLOGIST
	icon = "town"

/datum/award/achievement/mafia/chaplain
	name = "Chaplain Victory"
	desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector."
	database_id = MAFIA_MEDAL_CHAPLAIN
	icon = "town"

/datum/award/achievement/mafia/md
	name = "Medical Doctor Victory"
	desc = "Congratulations on learning how to not talk!"
	database_id = MAFIA_MEDAL_MD
	icon = "town"

/datum/award/achievement/mafia/officer
	name = "Security Officer Victory"
	desc = "Don't worry, you can win this if you're dead! You... did use your ability to become dead, right?"
	database_id = MAFIA_MEDAL_OFFICER
	icon = "town"

/datum/award/achievement/mafia/lawyer
	name = "Lawyer Victory"
	desc = "Oh don't mind me, i'm just the worst rol- Oops, I just instantly ended the game."
	database_id = MAFIA_MEDAL_LAWYER
	icon = "town"

/datum/award/achievement/mafia/hop
	name = "Head of Personnel Victory"
	desc = "King of Assistants, waster of a single mafia's night, thrower of games."
	database_id = MAFIA_MEDAL_HOP
	icon = "town"

/datum/award/achievement/mafia/warden
	name = "Warden Victory"
	desc = "Make changelings think you're detective, go on lockdown, actual detective investigates you and dies. Cha cha real smooth!"
	database_id = MAFIA_MEDAL_WARDEN
	icon = "town"

/datum/award/achievement/mafia/hos
	name = "Head of Security Victory"
	desc = "Certified not shitcurity."
	database_id = MAFIA_MEDAL_HOS
	icon = "town"

/datum/award/achievement/mafia/changeling
	name = "Changeling Victory"
	desc = "I think the changelings are metacomming."
	database_id = MAFIA_MEDAL_CHANGELING
	icon = "mafia"

/datum/award/achievement/mafia/thoughtfeeder
	name = "Thoughtfeeder Victory"
	desc = "Clown's best friend. And Obsessed. And fugitive? Whose side are you on?!"
	database_id = MAFIA_MEDAL_THOUGHTFEEDER
	icon = "mafia"

/datum/award/achievement/mafia/traitor
	name = "Traitor Victory"
	desc = "Guys, we still have two more changelings to ki-!! TRAITOR VICTORY !!"
	database_id = MAFIA_MEDAL_TRAITOR
	icon = "neutral"

/datum/award/achievement/mafia/nightmare
	name = "Nightmare Victory"
	desc = "DID YOUR LIGHT FLICKER?!"
	database_id = MAFIA_MEDAL_NIGHTMARE
	icon = "neutral"

/datum/award/achievement/mafia/fugitive
	name = "Fugitive Victory"
	desc = "I'm just the description on an achievement, but if you end up having to choose between town and changelings, go changelings."
	database_id = MAFIA_MEDAL_FUGITIVE
	icon = "neutral"

/datum/award/achievement/mafia/obsessed
	name = "Obsessed Victory"
	desc = "You got your target lynched, so instead of being spiteful and annoying, you're just smug and annoying."
	database_id = MAFIA_MEDAL_OBSESSED
	icon = "neutral"

/datum/award/achievement/mafia/clown
	name = "Clown Victory"
	desc = "Did you know this works on traitors, despite their immunity? If you hit the jackpot and manage to kill one, they'll salt into the next dimension. Clown tips!"
	database_id = MAFIA_MEDAL_CLOWN
	icon = "neutral"

///ALL THE ACHIEVEMENTS FOR MISC MAFIA ODDITIES///

/datum/award/achievement/mafia/universally_hated
	name = "Universally Hated"
	desc = "Managed to get more than 12 votes when put up on trial, jesus christ."
	database_id = MAFIA_MEDAL_HATED
	icon = "hated"


code:
/datum/award/achievement/misc
	category = "Misc"
	icon = "basemisc"

/datum/award/achievement/misc/meteor_examine
	name = "Your Life Before Your Eyes"
	desc = "Take a close look at hurtling space debris"
	database_id = MEDAL_METEOR
	icon = "meteors"

/datum/award/achievement/misc/pulse
	name = "Jackpot"
	desc = "Win a pulse rifle from an arcade machine"
	database_id = MEDAL_PULSE
	icon = "jackpot"

/datum/award/achievement/misc/time_waste
	name = "Time waster"
	desc = "Speak no evil, hear no evil, see just errors"
	database_id = MEDAL_TIMEWASTE
	icon = "timewaste"

/datum/award/achievement/misc/feat_of_strength
	name = "Feat of Strength"
	desc = "If the rod is immovable, is it passing you or are you passing it?"
	database_id = MEDAL_RODSUPLEX
	icon = "featofstrength"

/datum/award/achievement/misc/round_and_full
	name = "Round and Full"
	desc = "Well at least you aren't down the river, I hear they eat people there."
	database_id = MEDAL_CLOWNCARKING
	icon = "clownking"

/datum/award/achievement/misc/the_best_driver
	name = "The Best Driver"
	desc = "100 honks later"
	database_id = MEDAL_THANKSALOT
	icon = "clownthanks"

/datum/award/achievement/misc/helbitaljanken
	name = "Helbitaljanken"
	desc = "You janked hard"
	database_id = MEDAL_HELBITALJANKEN
	icon = "helbital"

/datum/award/achievement/misc/getting_an_upgrade
	name = "Getting an upgrade"
	desc = "Make your first unique material item!"
	database_id = MEDAL_MATERIALCRAFT

/datum/award/achievement/misc/rocket_holdup
	name = "Disk, Please!"
	desc = "Is the man currently pointing a loaded rocket launcher at your head point blank really dumb enough to pull the trigger? Do you really want to find out?"
	database_id = MEDAL_DISKPLEASE

/datum/award/achievement/misc/gamer
	name = "My Watchlist Status is Not Important"
	desc = "You may be under the impression that violent video games are a harmless pastime, but the security and medical personnel swarming your location with batons and knockout gas look like they disagree."
	database_id = MEDAL_GAMER

/datum/award/achievement/misc/vendor_squish
	name = "I Was a Teenage Anarchist"
	desc = "You were doing a great job sticking it to the system until that vending machine decided to fight back."
	database_id = MEDAL_VENDORSQUISH

/datum/award/achievement/misc/swirlie
	name = "A Bowl-d New World"
	desc = "There's a lot of grisly ways to kick it on the Spinward Periphery, but drowning to death in a toilet probably wasn't what you had in mind. Probably."
	database_id = MEDAL_SWIRLIE

/datum/award/achievement/misc/selfouch
	name = "How Do I Switch Hands???"
	desc = "If you saw someone casually club themselves upside the head with a toolbox anywhere in the galaxy but here, you'd probably be pretty concerned for them."
	database_id = MEDAL_SELFOUCH

/datum/award/achievement/misc/sandman
	name = "Mister Sandman"
	desc = "Mechanically speaking, there's no real benefit to being unconscious during surgery. Weird how insistent this doctor is about using the N2O anyway though, huh?"
	database_id = MEDAL_SANDMAN

/datum/award/achievement/misc/cleanboss
	name = "One Lean, Mean, Cleaning Machine"
	desc = "How does it feel to know that your workplace values a mop bucket on wheels more than you?" // i can do better than this give me time
	database_id = MEDAL_CLEANBOSS

/datum/award/achievement/misc/rule8
	name = "Rule 8"
	desc = "Call an admin this is ILLEGAL!!"
	database_id = MEDAL_RULE8
	icon = "rule8"

/datum/award/achievement/misc/speed_round
	name = "Long shift"
	desc = "Well, that didn't take long."
	database_id = MEDAL_LONGSHIFT
	icon = "longshift"

/datum/award/achievement/misc/snail
	name = "KKKiiilll mmmeee"
	desc = "You were a little too ambitious, but hey, I guess you're still alive?"
	database_id = MEDAL_SNAIL
	icon = "snail"

/datum/award/achievement/misc/lookoutsir
	name = "Look Out, Sir!"
	desc = "Either awarded for making the ultimate sacrifice for your comrades, or a really dumb attempt at grenade jumping."
	database_id = MEDAL_LOOKOUTSIR

/datum/award/achievement/misc/gottem
	name = "HA, GOTTEM"
	desc = "Made you look!"
	database_id = MEDAL_GOTTEM

/datum/award/achievement/misc/ascension
	name = "Ascension"
	desc = "Caedite eos. Novit enim Dominus qui sunt eius."
	database_id = MEDAL_ASCENSION
	icon = "ascension"

/datum/award/achievement/misc/frenching
	name = "Frenching"
	desc = "Just a taste, for science!"
	database_id = MEDAL_FRENCHING
	icon = "frenching"

/datum/award/achievement/misc/ash_ascension
	name = "Nightwatcher's Eyes"
	desc = "You've risen above the flames, became one with the ashes. You've been reborn as one with the Nightwatcher."
	database_id = MEDAL_ASH_ASCENSION
	icon = "ashascend"

/datum/award/achievement/misc/flesh_ascension
	name = "Vortex of Arms"
	desc = "You've became something more, something greater. A piece of the emperor resides within you, and you within him."
	database_id = MEDAL_FLESH_ASCENSION
	icon = "fleshascend"

/datum/award/achievement/misc/rust_ascension
	name = "Hills of Rust"
	desc = "You've summoned a piece of the Hill of rust, and so the Hills welcome you."
	database_id = MEDAL_RUST_ASCENSION
	icon = "rustascend"

/datum/award/achievement/misc/void_ascension
	name = "All that perish"
	desc = "Place of a different being, different time. Everything ends there... but maybe it is just the beginning?"
	database_id = MEDAL_VOID_ASCENSION
	icon = "voidascend"

/datum/award/achievement/misc/toolbox_soul
	name = "SOUL'd Out"
	desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..."
	database_id = MEDAL_TOOLBOX_SOUL
	icon = "toolbox_soul"

/datum/award/achievement/misc/hot_damn
	name = "Hot Damn!"
	desc = "Sometimes you need to make some noise to make a point."
	database_id = MEDAL_HOT_DAMN
	icon = "hotdamn"

/datum/award/achievement/misc/cayenne_disk
	name = "Very Important Piscis"
	desc = "You can rest well now."
	database_id = MEDAL_CAYENNE_DISK
	icon = "cayenne_disk"

/datum/award/achievement/misc/tram_surfer
	name = "Tram Surfer"
	desc = "Lights out, guerilla radio!"
	database_id = MEDAL_TRAM_SURFER
	icon = "tram_surfer"

/datum/award/achievement/misc/cult_shuttle_omfg
	name = "WHAT JUST HAPPENED"
	desc = "As a blood cultist, be part of a team that summons 3 shuttle curses within 10 seconds. Imagine cleaning up after them, g r o s s!"
	database_id = MEDAL_CULT_SHUTTLE_OMFG
	icon = "cult_shuttle_omfg"


code:
///How many times did we survive being a cripple?
/datum/award/score/hardcore_random
	name = "Hardcore random points"
	desc = "Well, I might be a blind, deaf, crippled guy, but hey, at least I'm alive."
	database_id = HARDCORE_RANDOM_SCORE

///How many maintenance pills did you eat?
/datum/award/score/maintenance_pill
	name = "Maintenance Pills Consumed"
	desc = "Wait why?"
	database_id = MAINTENANCE_PILL_SCORE

///How high of a score on the Intento did we get?
/datum/award/score/intento_score
	name = "Intento Score"
	desc = "A blast from the future?"
	database_id = INTENTO_SCORE


code:
/datum/award/achievement/skill
	category = "Skills"
	icon = "baseskill"

/datum/award/achievement/skill/legendary_miner
	name = "Legendary miner"
	desc = "No mere rock can stop me!"
	database_id = MEDAL_LEGENDARY_MINER
	icon = "mining"


code:
#define AB_CHECK_HANDS_BLOCKED (1<<0)
#define AB_CHECK_IMMOBILE (1<<1)
#define AB_CHECK_LYING (1<<2)
#define AB_CHECK_CONSCIOUS (1<<3)

/datum/action
	var/name = "Generic Action"
	var/desc
	var/datum/target
	var/check_flags = NONE
	var/processing = FALSE
	var/atom/movable/screen/movable/action_button/button = null
	var/buttontooltipstyle = ""
	var/transparent_when_unavailable = TRUE

	var/button_icon = 'icons/mob/actions/backgrounds.dmi' //This is the file for the BACKGROUND icon
	var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND //And this is the state for the background icon

	var/icon_icon = 'icons/hud/actions.dmi' //This is the file for the ACTION icon
	var/button_icon_state = "default" //And this is the state for the action icon
	var/mob/owner
	///All mobs that are sharing our action button.
	var/list/sharers = list()

/datum/action/New(Target)
	link_to(Target)
	button = new
	button.linked_action = src
	button.name = name
	button.actiontooltipstyle = buttontooltipstyle
	if(desc)
		button.desc = desc

/datum/action/proc/link_to(Target)
	target = Target
	RegisterSignal(Target, COMSIG_ATOM_UPDATED_ICON, .proc/OnUpdatedIcon)

/datum/action/Destroy()
	if(owner)
		Remove(owner)
	target = null
	QDEL_NULL(button)
	return ..()

/datum/action/proc/Grant(mob/M)
	if(M)
		if(owner)
			if(owner == M)
				return
			Remove(owner)
		owner = M
		RegisterSignal(owner, COMSIG_PARENT_QDELETING, .proc/owner_deleted)

		//button id generation
		var/counter = 0
		var/bitfield = 0
		for(var/datum/action/A in M.actions)
			if(A.name == name && A.button.id)
				counter += 1
				bitfield |= A.button.id
		bitfield = ~bitfield
		var/bitflag = 1
		for(var/i in 1 to (counter + 1))
			if(bitfield & bitflag)
				button.id = bitflag
				break
			bitflag *= 2

		LAZYADD(M.actions, src)
		if(M.client)
			M.client.screen += button
			button.locked = M.client.prefs.read_preference(/datum/preference/toggle/buttons_locked) || button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE //even if it's not defaultly locked we should remember we locked it before
			button.moved = button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE
		M.update_action_buttons()
	else
		Remove(owner)

/datum/action/proc/owner_deleted(datum/source)
	SIGNAL_HANDLER
	Remove(owner)

/datum/action/proc/Remove(mob/M)
	for(var/datum/weakref/reference as anything in sharers)
		var/mob/freeloader = reference.resolve()
		if(!freeloader)
			continue
		Unshare(freeloader)
	sharers = null
	if(M)
		if(M.client)
			M.client.screen -= button
		LAZYREMOVE(M.actions, src)
		M.update_action_buttons()
	if(owner)
		UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
		owner = null
	button.moved = FALSE //so the button appears in its normal position when given to another owner.
	button.locked = FALSE
	button.id = null

/datum/action/proc/Trigger()
	if(!IsAvailable())
		return FALSE
	if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
		return FALSE
	return TRUE

/datum/action/proc/IsAvailable()
	if(!owner)
		return FALSE
	if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
		return FALSE
	if((check_flags & AB_CHECK_IMMOBILE) && HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
		return FALSE
	if((check_flags & AB_CHECK_LYING) && isliving(owner))
		var/mob/living/action_user = owner
		if(action_user.body_position == LYING_DOWN)
			return FALSE
	if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS)
		return FALSE
	return TRUE

/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE)
	if(button)
		if(!status_only)
			button.name = name
			button.desc = desc
			if(owner?.hud_used && background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND)
				var/list/settings = owner.hud_used.get_action_buttons_icons()
				if(button.icon != settings["bg_icon"])
					button.icon = settings["bg_icon"]
				if(button.icon_state != settings["bg_state"])
					button.icon_state = settings["bg_state"]
			else
				if(button.icon != button_icon)
					button.icon = button_icon
				if(button.icon_state != background_icon_state)
					button.icon_state = background_icon_state

			ApplyIcon(button, force)

		if(!IsAvailable())
			button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
		else
			button.color = rgb(255,255,255,255)
			return 1

/datum/action/proc/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
	if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force))
		current_button.cut_overlays(TRUE)
		current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
		current_button.button_icon_state = button_icon_state

/datum/action/proc/OnUpdatedIcon()
	SIGNAL_HANDLER
	UpdateButtonIcon()

//Adds our action button to the screen of another player
/datum/action/proc/Share(mob/freeloader)
	if(!freeloader.client)
		return
	sharers += WEAKREF(freeloader)
	freeloader.client.screen += button
	freeloader.update_action_buttons()

//Removes our action button from the screen of another player
/datum/action/proc/Unshare(mob/freeloader)
	if(!freeloader.client)
		return
	for(var/freeloader_reference in sharers)
		if(IS_WEAKREF_OF(freeloader, freeloader_reference))
			sharers -= freeloader_reference
			break
	freeloader.client.screen -= button
	freeloader.update_action_buttons()

//Presets for item actions
/datum/action/item_action
	check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_CONSCIOUS
	button_icon_state = null
	// If you want to override the normal icon being the item
	// then change this to an icon state

/datum/action/item_action/New(Target)
	..()
	var/obj/item/I = target
	LAZYINITLIST(I.actions)
	I.actions += src

/datum/action/item_action/Destroy()
	var/obj/item/I = target
	I.actions -= src
	UNSETEMPTY(I.actions)
	return ..()

/datum/action/item_action/Trigger()
	. = ..()
	if(!.)
		return FALSE
	if(target)
		var/obj/item/I = target
		I.ui_action_click(owner, src)
	return TRUE

/datum/action/item_action/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force)
	var/obj/item/item_target = target
	if(button_icon && button_icon_state)
		// If set, use the custom icon that we set instead
		// of the item appearence
		..()
	else if((target && current_button.appearance_cache != item_target.appearance) || force) //replace with /ref comparison if this is not valid.
		var/old_layer = item_target.layer
		var/old_plane = item_target.plane
		item_target.layer = FLOAT_LAYER //AAAH
		item_target.plane = FLOAT_PLANE //^ what that guy said
		current_button.cut_overlays()
		current_button.add_overlay(item_target)
		item_target.layer = old_layer
		item_target.plane = old_plane
		current_button.appearance_cache = item_target.appearance

/datum/action/item_action/toggle_light
	name = "Toggle Light"

/datum/action/item_action/toggle_light/Trigger()
	if(istype(target, /obj/item/pda))
		var/obj/item/pda/P = target
		P.toggle_light(owner)
		return
	..()

/datum/action/item_action/toggle_hood
	name = "Toggle Hood"

/datum/action/item_action/toggle_firemode
	name = "Toggle Firemode"

/datum/action/item_action/rcl_col
	name = "Change Cable Color"
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "rcl_rainbow"

/datum/action/item_action/rcl_gui
	name = "Toggle Fast Wiring Gui"
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "rcl_gui"

/datum/action/item_action/startchainsaw
	name = "Pull The Starting Cord"

/datum/action/item_action/toggle_gunlight
	name = "Toggle Gunlight"

/datum/action/item_action/toggle_mode
	name = "Toggle Mode"

/datum/action/item_action/toggle_barrier_spread
	name = "Toggle Barrier Spread"

/datum/action/item_action/equip_unequip_ted_gun
	name = "Equip/Unequip TED Gun"

/datum/action/item_action/toggle_paddles
	name = "Toggle Paddles"

/datum/action/item_action/set_internals
	name = "Set Internals"

/datum/action/item_action/set_internals/UpdateButtonIcon(status_only = FALSE, force)
	if(..()) //button available
		if(iscarbon(owner))
			var/mob/living/carbon/C = owner
			if(target == C.internal)
				button.icon_state = "template_active"

/datum/action/item_action/pick_color
	name = "Choose A Color"

/datum/action/item_action/toggle_mister
	name = "Toggle Mister"

/datum/action/item_action/activate_injector
	name = "Activate Injector"

/datum/action/item_action/toggle_helmet_light
	name = "Toggle Helmet Light"

/datum/action/item_action/toggle_welding_screen
	name = "Toggle Welding Screen"

/datum/action/item_action/toggle_welding_screen/Trigger()
	var/obj/item/clothing/head/hardhat/weldhat/H = target
	if(istype(H))
		H.toggle_welding_screen(owner)

/datum/action/item_action/toggle_welding_screen/plasmaman
	name = "Toggle Welding Screen"

/datum/action/item_action/toggle_welding_screen/plasmaman/Trigger()
	var/obj/item/clothing/head/helmet/space/plasmaman/H = target
	if(istype(H))
		H.toggle_welding_screen(owner)

/datum/action/item_action/toggle_spacesuit
	name = "Toggle Suit Thermal Regulator"
	icon_icon = 'icons/mob/actions/actions_spacesuit.dmi'
	button_icon_state = "thermal_off"

/datum/action/item_action/toggle_spacesuit/New(Target)
	. = ..()
	RegisterSignal(target, COMSIG_SUIT_SPACE_TOGGLE, .proc/toggle)

/datum/action/item_action/toggle_spacesuit/Destroy()
	UnregisterSignal(target, COMSIG_SUIT_SPACE_TOGGLE)
	return ..()

/datum/action/item_action/toggle_spacesuit/Trigger()
	var/obj/item/clothing/suit/space/suit = target
	if(!istype(suit))
		return
	suit.toggle_spacesuit()

/// Toggle the action icon for the space suit thermal regulator
/datum/action/item_action/toggle_spacesuit/proc/toggle(obj/item/clothing/suit/space/suit)
	SIGNAL_HANDLER

	button_icon_state = "thermal_[suit.thermal_on ? "on" : "off"]"
	UpdateButtonIcon()

/datum/action/item_action/vortex_recall
	name = "Vortex Recall"
	desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it."
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "vortex_recall"

/datum/action/item_action/vortex_recall/IsAvailable()
	var/area/current_area = get_area(target)
	if(current_area.area_flags & NOTELEPORT)
		to_chat(owner, span_notice("[target] fizzles uselessly."))
		return
	if(istype(target, /obj/item/hierophant_club))
		var/obj/item/hierophant_club/H = target
		if(H.teleporting)
			return FALSE
	return ..()

/datum/action/item_action/berserk_mode
	name = "Berserk"
	desc = "Increase your movement and melee speed while also increasing your melee armor for a short amount of time."
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "berserk_mode"
	background_icon_state = "bg_demon"

/datum/action/item_action/berserk_mode/Trigger()
	if(istype(target, /obj/item/clothing/head/helmet/space/hardsuit/berserker))
		var/obj/item/clothing/head/helmet/space/hardsuit/berserker/berzerk = target
		if(berzerk.berserk_active)
			to_chat(owner, span_warning("You are already berserk!"))
			return
		if(berzerk.berserk_charge < 100)
			to_chat(owner, span_warning("You don't have a full charge."))
			return
		berzerk.berserk_mode(owner)
		return
	..()

/datum/action/item_action/toggle_helmet_flashlight
	name = "Toggle Helmet Flashlight"

/datum/action/item_action/toggle_helmet_mode
	name = "Toggle Helmet Mode"

/datum/action/item_action/crew_monitor
	name = "Interface With Crew Monitor"

/datum/action/item_action/toggle

/datum/action/item_action/toggle/New(Target)
	..()
	var/obj/item/item_target = target
	name = "Toggle [item_target.name]"
	button.name = name

/datum/action/item_action/halt
	name = "HALT!"

/datum/action/item_action/toggle_voice_box
	name = "Toggle Voice Box"

/datum/action/item_action/change
	name = "Change"

/datum/action/item_action/nano_picket_sign
	name = "Retext Nano Picket Sign"
	var/obj/item/picket_sign/S

/datum/action/item_action/nano_picket_sign/New(Target)
	..()
	if(istype(Target, /obj/item/picket_sign))
		S = Target

/datum/action/item_action/nano_picket_sign/Trigger()
	if(istype(S))
		S.retext(owner)

/datum/action/item_action/adjust

/datum/action/item_action/adjust/New(Target)
	..()
	var/obj/item/item_target = target
	name = "Adjust [item_target.name]"
	button.name = name

/datum/action/item_action/switch_hud
	name = "Switch HUD"

/datum/action/item_action/toggle_human_head
	name = "Toggle Human Head"

/datum/action/item_action/toggle_helmet
	name = "Toggle Helmet"

/datum/action/item_action/toggle_jetpack
	name = "Toggle Jetpack"

/datum/action/item_action/jetpack_stabilization
	name = "Toggle Jetpack Stabilization"

/datum/action/item_action/jetpack_stabilization/IsAvailable()
	var/obj/item/tank/jetpack/J = target
	if(!istype(J) || !J.on)
		return FALSE
	return ..()

/datum/action/item_action/hands_free
	check_flags = AB_CHECK_CONSCIOUS

/datum/action/item_action/hands_free/activate
	name = "Activate"

/datum/action/item_action/hands_free/shift_nerves
	name = "Shift Nerves"

/datum/action/item_action/explosive_implant
	check_flags = NONE
	name = "Activate Explosive Implant"

/datum/action/item_action/toggle_research_scanner
	name = "Toggle Research Scanner"
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "scan_mode"
	var/active = FALSE

/datum/action/item_action/toggle_research_scanner/Trigger()
	if(IsAvailable())
		active = !active
		if(active)
			owner.research_scanner++
		else
			owner.research_scanner--
		to_chat(owner, span_notice("[target] research scanner has been [active ? "activated" : "deactivated"]."))
		return 1

/datum/action/item_action/toggle_research_scanner/Remove(mob/M)
	if(owner && active)
		owner.research_scanner--
		active = FALSE
	..()

/datum/action/item_action/instrument
	name = "Use Instrument"
	desc = "Use the instrument specified"

/datum/action/item_action/instrument/Trigger()
	if(istype(target, /obj/item/instrument))
		var/obj/item/instrument/I = target
		I.interact(usr)
		return
	return ..()

/datum/action/item_action/activate_remote_view
	name = "Activate Remote View"
	desc = "Activates the Remote View of your spy sunglasses."

/datum/action/item_action/organ_action
	check_flags = AB_CHECK_CONSCIOUS

/datum/action/item_action/organ_action/IsAvailable()
	var/obj/item/organ/I = target
	if(!I.owner)
		return FALSE
	return ..()

/datum/action/item_action/organ_action/toggle/New(Target)
	..()
	var/obj/item/organ/organ_target = target
	name = "Toggle [organ_target.name]"
	button.name = name

/datum/action/item_action/organ_action/use/New(Target)
	..()
	var/obj/item/organ/organ_target = target
	name = "Use [organ_target.name]"
	button.name = name

/datum/action/item_action/cult_dagger
	name = "Draw Blood Rune"
	desc = "Use the ritual dagger to create a powerful blood rune"
	icon_icon = 'icons/mob/actions/actions_cult.dmi'
	button_icon_state = "draw"
	buttontooltipstyle = "cult"
	background_icon_state = "bg_demon"

/datum/action/item_action/cult_dagger/Grant(mob/M)
	if(IS_CULTIST(M))
		..()
		button.screen_loc = "6:157,4:-2"
		button.moved = "6:157,4:-2"
	else
		Remove(owner)

/datum/action/item_action/cult_dagger/Trigger()
	for(var/obj/item/H in owner.held_items) //In case we were already holding another dagger
		if(istype(H, /obj/item/melee/cultblade/dagger))
			H.attack_self(owner)
			return
	var/obj/item/I = target
	if(owner.can_equip(I, ITEM_SLOT_HANDS))
		owner.temporarilyRemoveItemFromInventory(I)
		owner.put_in_hands(I)
		I.attack_self(owner)
		return
	if(!isliving(owner))
		to_chat(owner, span_warning("You lack the necessary living force for this action."))
		return
	var/mob/living/living_owner = owner
	if (living_owner.usable_hands <= 0)
		to_chat(living_owner, span_warning("You dont have any usable hands!"))
	else
		to_chat(living_owner, span_warning("Your hands are full!"))

///MGS BOX!
/datum/action/item_action/agent_box
	name = "Deploy Box"
	desc = "Find inner peace, here, in the box."
	check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
	background_icon_state = "bg_agent"
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "deploy_box"
	///The type of closet this action spawns.
	var/boxtype = /obj/structure/closet/cardboard/agent
	COOLDOWN_DECLARE(box_cooldown)

///Handles opening and closing the box.
/datum/action/item_action/agent_box/Trigger()
	. = ..()
	if(!.)
		return FALSE
	if(istype(owner.loc, /obj/structure/closet/cardboard/agent))
		var/obj/structure/closet/cardboard/agent/box = owner.loc
		owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
		box.open()
		return
	//Box closing from here on out.
	if(!isturf(owner.loc)) //Don't let the player use this to escape mechs/welded closets.
		to_chat(owner, span_warning("You need more space to activate this implant!"))
		return
	if(!COOLDOWN_FINISHED(src, box_cooldown))
		return
	COOLDOWN_START(src, box_cooldown, 10 SECONDS)
	var/box = new boxtype(owner.drop_location())
	owner.forceMove(box)
	owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)

//Preset for spells
/datum/action/spell_action
	check_flags = NONE
	background_icon_state = "bg_spell"

/datum/action/spell_action/New(Target)
	..()
	var/obj/effect/proc_holder/S = target
	S.action = src
	name = S.name
	desc = S.desc
	icon_icon = S.action_icon
	button_icon_state = S.action_icon_state
	background_icon_state = S.action_background_icon_state
	button.name = name

/datum/action/spell_action/Destroy()
	var/obj/effect/proc_holder/S = target
	S.action = null
	return ..()

/datum/action/spell_action/Trigger()
	if(!..())
		return FALSE
	if(target)
		var/obj/effect/proc_holder/S = target
		S.Click()
		return TRUE

/datum/action/spell_action/IsAvailable()
	if(!target)
		return FALSE
	return TRUE

/datum/action/spell_action/spell

/datum/action/spell_action/spell/IsAvailable()
	if(!target)
		return FALSE
	var/obj/effect/proc_holder/spell/S = target
	if(owner)
		return S.can_cast(owner)
	return FALSE

/datum/action/spell_action/alien

/datum/action/spell_action/alien/IsAvailable()
	if(!target)
		return FALSE
	var/obj/effect/proc_holder/alien/ab = target
	if(owner)
		return ab.cost_check(ab.check_turf,owner,1)
	return FALSE

//Preset for general and toggled actions
/datum/action/innate
	check_flags = NONE
	var/active = 0

/datum/action/innate/Trigger()
	if(!..())
		return FALSE
	if(!active)
		Activate()
	else
		Deactivate()
	return TRUE

/datum/action/innate/proc/Activate()
	return

/datum/action/innate/proc/Deactivate()
	return

//Preset for an action with a cooldown

/datum/action/cooldown
	check_flags = NONE
	transparent_when_unavailable = FALSE
	var/cooldown_time = 0
	var/next_use_time = 0

/datum/action/cooldown/New()
	..()
	button.maptext = ""
	button.maptext_x = 8
	button.maptext_y = 0
	button.maptext_width = 24
	button.maptext_height = 12

/datum/action/cooldown/IsAvailable()
	return next_use_time <= world.time

/datum/action/cooldown/proc/StartCooldown()
	next_use_time = world.time + cooldown_time
	button.maptext = MAPTEXT("[round(cooldown_time/10, 0.1)]")
	UpdateButtonIcon()
	START_PROCESSING(SSfastprocess, src)

/datum/action/cooldown/process()
	if(!owner)
		button.maptext = ""
		STOP_PROCESSING(SSfastprocess, src)
	var/timeleft = max(next_use_time - world.time, 0)
	if(timeleft == 0)
		button.maptext = ""
		UpdateButtonIcon()
		STOP_PROCESSING(SSfastprocess, src)
	else
		button.maptext = MAPTEXT("[round(timeleft/10, 0.1)]")

/datum/action/cooldown/Grant(mob/M)
	..()
	if(owner)
		UpdateButtonIcon()
		if(next_use_time > world.time)
			START_PROCESSING(SSfastprocess, src)

///Like a cooldown action, but with an associated proc holder.
/datum/action/cooldown/spell_like

/datum/action/cooldown/spell_like/New(Target)
	..()
	var/obj/effect/proc_holder/our_proc_holder = target
	our_proc_holder.action = src
	name = our_proc_holder.name
	desc = our_proc_holder.desc
	icon_icon = our_proc_holder.action_icon
	button_icon_state = our_proc_holder.action_icon_state
	background_icon_state = our_proc_holder.action_background_icon_state
	button.name = name

/datum/action/cooldown/spell_like/Trigger()
	if(!..())
		return FALSE
	if(target)
		var/obj/effect/proc_holder/our_proc_holder = target
		our_proc_holder.Click()
		return TRUE

//Stickmemes
/datum/action/item_action/stickmen
	name = "Summon Stick Minions"
	desc = "Allows you to summon faithful stickmen allies to aide you in battle."
	icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
	button_icon_state = "art_summon"

//surf_ss13
/datum/action/item_action/bhop
	name = "Activate Jump Boots"
	desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps."
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "jetboot"

/datum/action/item_action/bhop/brocket
	name = "Activate Rocket Boots"
	desc = "Activates the boot's rocket propulsion system, allowing the user to hurl themselves great distances."

/datum/action/language_menu
	name = "Language Menu"
	desc = "Open the language menu to review your languages, their keys, and select your default language."
	button_icon_state = "language_menu"
	check_flags = NONE

/datum/action/language_menu/Trigger()
	if(!..())
		return FALSE
	if(ismob(owner))
		var/mob/M = owner
		var/datum/language_holder/H = M.get_language_holder()
		H.open_language_menu(usr)

/datum/action/item_action/wheelys
	name = "Toggle Wheels"
	desc = "Pops out or in your shoes' wheels."
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "wheelys"

/datum/action/item_action/kindle_kicks
	name = "Activate Kindle Kicks"
	desc = "Kick you feet together, activating the lights in your Kindle Kicks."
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "kindleKicks"

//Small sprites
/datum/action/small_sprite
	name = "Toggle Giant Sprite"
	desc = "Others will always see you as giant"
	icon_icon = 'icons/mob/actions/actions_xeno.dmi'
	button_icon_state = "smallqueen"
	background_icon_state = "bg_alien"
	var/small = FALSE
	var/small_icon
	var/small_icon_state

/datum/action/small_sprite/queen
	small_icon = 'icons/mob/alien.dmi'
	small_icon_state = "alienq"

/datum/action/small_sprite/megafauna
	icon_icon = 'icons/mob/actions/actions_xeno.dmi'
	button_icon_state = "smallqueen"
	background_icon_state = "bg_alien"
	small_icon = 'icons/mob/lavaland/lavaland_monsters.dmi'

/datum/action/small_sprite/megafauna/drake
	small_icon_state = "ash_whelp"

/datum/action/small_sprite/megafauna/colossus
	small_icon_state = "Basilisk"

/datum/action/small_sprite/megafauna/bubblegum
	small_icon_state = "goliath2"

/datum/action/small_sprite/megafauna/legion
	small_icon_state = "mega_legion"

/datum/action/small_sprite/mega_arachnid
	small_icon = 'icons/mob/jungle/arachnid.dmi'
	small_icon_state = "arachnid_mini"
	background_icon_state = "bg_demon"

/datum/action/small_sprite/Trigger()
	..()
	if(!small)
		var/image/I = image(icon = small_icon, icon_state = small_icon_state, loc = owner)
		I.override = TRUE
		I.pixel_x -= owner.pixel_x
		I.pixel_y -= owner.pixel_y
		owner.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic, "smallsprite", I, AA_TARGET_SEE_APPEARANCE | AA_MATCH_TARGET_OVERLAYS)
		small = TRUE
	else
		owner.remove_alt_appearance("smallsprite")
		small = FALSE

/datum/action/item_action/storage_gather_mode
	name = "Switch gathering mode"
	desc = "Switches the gathering mode of a storage object."
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "storage_gather_switch"

/datum/action/item_action/storage_gather_mode/ApplyIcon(atom/movable/screen/movable/action_button/current_button)
	. = ..()
	var/obj/item/item_target = target
	var/old_layer = item_target.layer
	var/old_plane = item_target.plane
	item_target.layer = FLOAT_LAYER //AAAH
	item_target.plane = FLOAT_PLANE //^ what that guy said
	current_button.cut_overlays()
	current_button.add_overlay(target)
	item_target.layer = old_layer
	item_target.plane = old_plane
	current_button.appearance_cache = item_target.appearance


code:
/datum/action/item_action/zoom_speed_action
	name = "Toggle Zooming Speed"
	icon_icon = 'icons/mob/actions/actions_spells.dmi'
	button_icon_state = "projectile"
	background_icon_state = "bg_tech"

/datum/action/item_action/zoom_lock_action
	name = "Switch Zoom Mode"
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	button_icon_state = "zoom_mode"
	background_icon_state = "bg_tech"


code:
# AI controllers

## Introduction

Our AI controller system is an attempt at making it possible to create modularized AI that stores its behavior in datums, while keeping state and decision making in a controller. This allows a more versatile way of creating AI that doesn't rely on OOP as much, and doesn't clutter up the Life() code in Mobs.

## AI Controllers

A datum that can be added to any atom in the game. Similarly to components, they might only support a given subtype (e.g. /mob/living), but the idea is that theoretically, you could apply a specific AI controller to a big a group of different types as possible and it would still work.

These datums handle both the normal movement of mobs, but also their decision making, deciding which actions they will take based on the checks you put into their SelectBehaviors proc.

If behaviors are selected, and the AI is in range, it will try to perform them. It runs all the behaviors it currently has in parallel; allowing for it to for example screech at someone while trying to attack them. Aslong as it has behaviors running, it will not try to generate new plans, making it not waste CPU when it already has an active goal.

They also hold data for any of the actions they might need to use, such as cooldowns, whether or not they're currently fighting, etcetera this is stored in the blackboard, more information on that below.

### Blackboard
The blackboard is an associated list keyed with strings and with values of whatever you want. These store information the mob has such as "Am I attacking someone", "Do I have a weapon". By using an associated list like this, no data needs to be stored on the actions themselves, and you could make actions that work on multiple ai controllers if you so pleased by making the key to use a variable.

## AI Behavior
AI behaviors are the actions an AI can take. These can range from "Do an emote" to "Attack this target until he is dead". They are singletons and should contain nothing but static data. Any dynamic data should be stored in the blackboard, to allow different controllers to use the same behaviors.

# Guides:

[Making Your AI](./making_your_ai.md): Quickly runs through how to make an ai controller for anything with a step by step development of one.


code:
///Abstract class for an action an AI can take, can range from movement to grabbing a nearby weapon.
/datum/ai_behavior
	///What distance you need to be from the target to perform the action
	var/required_distance = 1
	///Flags for extra behavior
	var/behavior_flags = NONE
	///Cooldown between actions performances, defaults to the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior
	var/action_cooldown = CLICK_CD_MELEE

/// Called by the ai controller when first being added. Additional arguments depend on the behavior type.
/// Return FALSE to cancel
/datum/ai_behavior/proc/setup(datum/ai_controller/controller, ...)
	return TRUE

///Called by the AI controller when this action is performed
/datum/ai_behavior/proc/perform(delta_time, datum/ai_controller/controller, ...)
	controller.behavior_cooldowns[src] = world.time + action_cooldown
	return

///Called when the action is finished. This needs the same args as perform besides the default ones
/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded, ...)
	LAZYREMOVE(controller.current_behaviors, src)
	controller.behavior_args -= type
	if(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //If this was a movement task, reset our movement target.
		controller.current_movement_target = null
		controller.ai_movement.stop_moving_towards(controller)


code:
/*
AI controllers are a datumized form of AI that simulates the input a player would otherwise give to a atom. What this means is that these datums
have ways of interacting with a specific atom and control it. They posses a blackboard with the information the AI knows and has, and will plan behaviors it will try to execute through
multiple modular subtrees with behaviors
*/

/datum/ai_controller
	///The atom this controller is controlling
	var/atom/pawn
	///Bitfield of traits for this AI to handle extra behavior
	var/ai_traits
	///Current actions being performed by the AI.
	var/list/current_behaviors
	///Current actions and their respective last time ran as an assoc list.
	var/list/behavior_cooldowns = list()
	///Current status of AI (OFF/ON)
	var/ai_status
	///Current movement target of the AI, generally set by decision making.
	var/atom/current_movement_target
	///This is a list of variables the AI uses and can be mutated by actions. When an action is performed you pass this list and any relevant keys for the variables it can mutate.
	var/list/blackboard = list()
	///Stored arguments for behaviors given during their initial creation
	var/list/behavior_args = list()
	///Tracks recent pathing attempts, if we fail too many in a row we fail our current plans.
	var/pathing_attempts
	///Can the AI remain in control if there is a client?
	var/continue_processing_when_client = FALSE
	///distance to give up on target
	var/max_target_distance = 14
	///Cooldown for new plans, to prevent AI from going nuts if it can't think of new plans and looping on end
	COOLDOWN_DECLARE(failed_planning_cooldown)
	///All subtrees this AI has available, will run them in order, so make sure they're in the order you want them to run. On initialization of this type, it will start as a typepath(s) and get converted to references of ai_subtrees found in SSai_controllers when init_subtrees() is called
	var/list/planning_subtrees

	///The idle behavior this AI performs when it has no actions.
	var/datum/idle_behavior/idle_behavior = null

	// Movement related things here
	///Reference to the movement datum we use. Is a type on initialize but becomes a ref afterwards.
	var/datum/ai_movement/ai_movement = /datum/ai_movement/dumb
	///Cooldown until next movement
	COOLDOWN_DECLARE(movement_cooldown)
	///Delay between movements. This is on the controller so we can keep the movement datum singleton
	var/movement_delay = 0.1 SECONDS

	// The variables below are fucking stupid and should be put into the blackboard at some point.
	///A list for the path we're currently following, if we're using JPS pathing
	var/list/movement_path
	///Cooldown for JPS movement, how often we're allowed to try making a new path
	COOLDOWN_DECLARE(repath_cooldown)
	///AI paused time
	var/paused_until = 0

/datum/ai_controller/New(atom/new_pawn)
	change_ai_movement_type(ai_movement)
	init_subtrees()

	if(idle_behavior)
		idle_behavior = new idle_behavior()

	PossessPawn(new_pawn)

/datum/ai_controller/Destroy(force, ...)
	set_ai_status(AI_STATUS_OFF)
	UnpossessPawn(FALSE)
	return ..()

///Overrides the current ai_movement of this controller with a new one
/datum/ai_controller/proc/change_ai_movement_type(datum/ai_movement/new_movement)
	ai_movement = SSai_movement.movement_types[new_movement]

///Completely replaces the planning_subtrees with a new set based on argument provided, list provided must contain specifically typepaths
/datum/ai_controller/proc/replace_planning_subtrees(list/typepaths_of_new_subtrees)
	planning_subtrees = typepaths_of_new_subtrees
	init_subtrees()

///Loops over the subtrees in planning_subtrees and looks at the ai_controllers to grab a reference, ENSURE planning_subtrees ARE TYPEPATHS AND NOT INSTANCES/REFERENCES BEFORE EXECUTING THIS
/datum/ai_controller/proc/init_subtrees()
	if(!LAZYLEN(planning_subtrees))
		return
	var/list/temp_subtree_list = list()
	for(var/subtree in planning_subtrees)
		var/subtree_instance = SSai_controllers.ai_subtrees[subtree]
		temp_subtree_list += subtree_instance
	planning_subtrees = temp_subtree_list

///Proc to move from one pawn to another, this will destroy the target's existing controller.
/datum/ai_controller/proc/PossessPawn(atom/new_pawn)
	if(pawn) //Reset any old signals
		UnpossessPawn(FALSE)

	if(istype(new_pawn.ai_controller)) //Existing AI, kill it.
		QDEL_NULL(new_pawn.ai_controller)

	if(TryPossessPawn(new_pawn) & AI_CONTROLLER_INCOMPATIBLE)
		qdel(src)
		CRASH("[src] attached to [new_pawn] but these are not compatible!")

	pawn = new_pawn
	pawn.ai_controller = src

	if(!continue_processing_when_client && istype(new_pawn, /mob))
		var/mob/possible_client_holder = new_pawn
		if(possible_client_holder.client)
			set_ai_status(AI_STATUS_OFF)
		else
			set_ai_status(AI_STATUS_ON)
	else
		set_ai_status(AI_STATUS_ON)

	RegisterSignal(pawn, COMSIG_MOB_LOGIN, .proc/on_sentience_gained)

///Abstract proc for initializing the pawn to the new controller
/datum/ai_controller/proc/TryPossessPawn(atom/new_pawn)
	return

///Proc for deinitializing the pawn to the old controller
/datum/ai_controller/proc/UnpossessPawn(destroy)
	UnregisterSignal(pawn, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT))
	pawn.ai_controller = null
	pawn = null
	if(destroy)
		qdel(src)
	return

///Returns TRUE if the ai controller can actually run at the moment.
/datum/ai_controller/proc/able_to_run()
	if(world.time < paused_until)
		return FALSE
	return TRUE

///Runs any actions that are currently running
/datum/ai_controller/process(delta_time)
	if(!able_to_run())
		walk(pawn, 0) //stop moving
		return //this should remove them from processing in the future through event-based stuff.

	if(!LAZYLEN(current_behaviors) && idle_behavior)
		idle_behavior.perform_idle_behavior(delta_time, src) //Do some stupid shit while we have nothing to do
		return

	if(current_movement_target && get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range
		CancelActions()
		return

	for(var/i in current_behaviors)
		var/datum/ai_behavior/current_behavior = i

		// Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior
		// Then pick the max of this and the delta_time passed to ai_controller.process()
		// Action cooldowns cannot happen faster than delta_time, so delta_time should be the value used in this scenario.
		var/action_delta_time = max(current_behavior.action_cooldown * 0.1, delta_time)

		if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //Might need to move closer
			if(!current_movement_target)
				stack_trace("[pawn] wants to perform action type [current_behavior.type] which requires movement, but has no current movement target!")
				return //This can cause issues, so don't let these slide.
			if(current_behavior.required_distance >= get_dist(pawn, current_movement_target)) ///Are we close enough to engage?
				if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.
					ai_movement.stop_moving_towards(src)

				if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
					continue
				ProcessBehavior(action_delta_time, current_behavior)
				return

			else if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it.
				ai_movement.start_moving_towards(src, current_movement_target, current_behavior.required_distance) //Then start moving

			if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so.
				if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
					continue
				ProcessBehavior(action_delta_time, current_behavior)
				return
		else //No movement required
			if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
				continue
			ProcessBehavior(action_delta_time, current_behavior)
			return

///This is where you decide what actions are taken by the AI.
/datum/ai_controller/proc/SelectBehaviors(delta_time)
	SHOULD_NOT_SLEEP(TRUE) //Fuck you don't sleep in procs like this.
	if(!COOLDOWN_FINISHED(src, failed_planning_cooldown))
		return FALSE

	LAZYINITLIST(current_behaviors)

	if(LAZYLEN(planning_subtrees))
		for(var/datum/ai_planning_subtree/subtree as anything in planning_subtrees)
			if(subtree.SelectBehaviors(src, delta_time) == SUBTREE_RETURN_FINISH_PLANNING)
				break

///This proc handles changing ai status, and starts/stops processing if required.
/datum/ai_controller/proc/set_ai_status(new_ai_status)
	if(ai_status == new_ai_status)
		return FALSE //no change

	ai_status = new_ai_status
	switch(ai_status)
		if(AI_STATUS_ON)
			SSai_controllers.active_ai_controllers += src
			START_PROCESSING(SSai_behaviors, src)
		if(AI_STATUS_OFF)
			STOP_PROCESSING(SSai_behaviors, src)
			SSai_controllers.active_ai_controllers -= src
			CancelActions()

/datum/ai_controller/proc/PauseAi(time)
	paused_until = world.time + time

///Call this to add a behavior to the stack.
/datum/ai_controller/proc/queue_behavior(behavior_type, ...)
	var/datum/ai_behavior/behavior = GET_AI_BEHAVIOR(behavior_type)
	if(!behavior)
		CRASH("Behavior [behavior_type] not found.")
	var/list/arguments = args.Copy()
	arguments[1] = src
	if(!behavior.setup(arglist(arguments)))
		return
	LAZYADD(current_behaviors, behavior)
	arguments.Cut(1, 2)
	if(length(arguments))
		behavior_args[behavior_type] = arguments
	else
		behavior_args -= behavior_type

/datum/ai_controller/proc/ProcessBehavior(delta_time, datum/ai_behavior/behavior)
	var/list/arguments = list(delta_time, src)
	var/list/stored_arguments = behavior_args[behavior.type]
	if(stored_arguments)
		arguments += stored_arguments
	behavior.perform(arglist(arguments))

/datum/ai_controller/proc/CancelActions()
	if(!LAZYLEN(current_behaviors))
		return
	for(var/i in current_behaviors)
		var/datum/ai_behavior/current_behavior = i
		var/list/arguments = list(src, FALSE)
		var/list/stored_arguments = behavior_args[current_behavior.type]
		if(stored_arguments)
			arguments += stored_arguments
		current_behavior.finish_action(arglist(arguments))

/datum/ai_controller/proc/on_sentience_gained()
	SIGNAL_HANDLER
	UnregisterSignal(pawn, COMSIG_MOB_LOGIN)
	if(!continue_processing_when_client)
		set_ai_status(AI_STATUS_OFF) //Can't do anything while player is connected
	RegisterSignal(pawn, COMSIG_MOB_LOGOUT, .proc/on_sentience_lost)

/datum/ai_controller/proc/on_sentience_lost()
	SIGNAL_HANDLER
	UnregisterSignal(pawn, COMSIG_MOB_LOGOUT)
	set_ai_status(AI_STATUS_ON) //Can't do anything while player is connected
	RegisterSignal(pawn, COMSIG_MOB_LOGIN, .proc/on_sentience_gained)

/// Use this proc to define how your controller defines what access the pawn has for the sake of pathfinding, likely pointing to whatever ID slot is relevant
/datum/ai_controller/proc/get_access()
	return


code:
///A subtree is attached to a controller and is occasionally called by /ai_controller/SelectBehaviors(), this mainly exists to act as a way to subtype and modify SelectBehaviors() without needing to subtype the ai controller itself
/datum/ai_planning_subtree

///Determines what behaviors should the controller try processing; if this returns SUBTREE_RETURN_FINISH_PLANNING then the controller won't go through the other subtrees should multiple exist in controller.planning_subtrees
/datum/ai_planning_subtree/proc/SelectBehaviors(datum/ai_controller/controller, delta_time)
	return


code:
///This behavior is for obj/items, it is used to free themselves out of the hands of whoever is holding them
/datum/ai_behavior/item_escape_grasp

/datum/ai_behavior/item_escape_grasp/perform(delta_time, datum/ai_controller/controller)
	. = ..()
	var/obj/item/item_pawn = controller.pawn
	var/mob/item_holder = item_pawn.loc
	if(!istype(item_holder))
		finish_action(controller, FALSE) //We're no longer beind held. abort abort!!
	item_pawn.visible_message(span_warning("[item_pawn] slips out of the hands of [item_holder]!"))
	item_holder.dropItemToGround(item_pawn, TRUE)
	finish_action(controller, TRUE)

///This behavior is for obj/items, it is used to move closer to a target and throw themselves towards them.
/datum/ai_behavior/item_move_close_and_attack
	required_distance = 3
	behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
	action_cooldown = 20
	///Sound to use
	var/attack_sound
	///Max attemps to make
	var/max_attempts = 3

/datum/ai_behavior/item_move_close_and_attack/setup(datum/ai_controller/controller, target_key, throw_count_key)
	. = ..()
	controller.current_movement_target = controller.blackboard[target_key]

/datum/ai_behavior/item_move_close_and_attack/perform(delta_time, datum/ai_controller/controller, target_key, throw_count_key)
	. = ..()
	var/obj/item/item_pawn = controller.pawn
	var/atom/throw_target = controller.blackboard[target_key]

	item_pawn.visible_message(span_warning("[item_pawn] hurls towards [throw_target]!"))
	item_pawn.throw_at(throw_target, rand(4,5), 9)
	playsound(item_pawn.loc, attack_sound, 100, TRUE)
	controller.blackboard[throw_count_key]++
	if(controller.blackboard[throw_count_key] >= max_attempts)
		finish_action(controller, TRUE, target_key, throw_count_key)

/datum/ai_behavior/item_move_close_and_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key)
	. = ..()
	reset_blackboard(controller, succeeded, target_key, throw_count_key)

/datum/ai_behavior/item_move_close_and_attack/proc/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key)
	controller.blackboard -= target_key
	controller.blackboard[throw_count_key] = 0

/datum/ai_behavior/item_move_close_and_attack/haunted
	attack_sound = 'sound/items/haunted/ghostitemattack.ogg'
	max_attempts = 4

/datum/ai_behavior/item_move_close_and_attack/haunted/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key)
	var/atom/throw_target = controller.blackboard[target_key]
	var/list/hauntee_list = controller.blackboard[BB_TO_HAUNT_LIST]
	hauntee_list[throw_target]--
	return ..()


code:
/datum/ai_behavior/break_spine/bane/finish_action(datum/ai_controller/controller, succeeded)
	var/list/bane_quotes = strings("bane.json", "bane")
	var/mob/living/bane = controller.pawn
	bane.say(pick(bane_quotes))
	. = ..()


code:
/*
You yourself fought the decadence of Gotham for years with all your strength, all your resources, all your moral authority.
And the only victory you achieved was a lie. Now you understand Gotham is beyond saving, and must be allowed to die.
*/
/datum/ai_controller/bane
	movement_delay = 0.4 SECONDS
	blackboard = list(BB_BANE_BATMAN = null)
	planning_subtrees = list(/datum/ai_planning_subtree/bane_hunting)

/datum/ai_controller/bane/TryPossessPawn(atom/new_pawn)
	if(!isliving(new_pawn))
		return AI_CONTROLLER_INCOMPATIBLE
	return ..() //Run parent at end

/datum/ai_controller/bane/able_to_run()
	var/mob/living/living_pawn = pawn
	if(IS_DEAD_OR_INCAP(living_pawn))
		return FALSE
	return ..()


code:
///The bat is broken!
/datum/ai_planning_subtree/bane_hunting/SelectBehaviors(datum/ai_controller/controller, delta_time)
	var/mob/living/batman = controller.blackboard[BB_BANE_BATMAN]
	if(!batman)
		for(var/mob/living/possibly_the_dark_knight in oview(7, controller.pawn))
			if(IS_DEAD_OR_INCAP(possibly_the_dark_knight)) //I HAVE BROKEN THE BAT
				continue
			controller.blackboard[BB_BANE_BATMAN] = possibly_the_dark_knight
			batman = possibly_the_dark_knight
			break
	if(batman)
		controller.queue_behavior(/datum/ai_behavior/break_spine/bane, BB_BANE_BATMAN)
		return SUBTREE_RETURN_FINISH_PLANNING


code:
/datum/ai_controller/basic_controller
	movement_delay = 0.4 SECONDS

/datum/ai_controller/basic_controller/TryPossessPawn(atom/new_pawn)
	if(!isbasicmob(new_pawn))
		return AI_CONTROLLER_INCOMPATIBLE
	var/mob/living/basic/basic_mob = new_pawn

	update_speed(basic_mob)

	RegisterSignal(basic_mob, POST_BASIC_MOB_UPDATE_VARSPEED, .proc/update_speed)

	return ..() //Run parent at end

/datum/ai_controller/basic_controller/able_to_run()
	. = ..()
	if(isliving(pawn))
		var/mob/living/living_pawn = pawn
		if(IS_DEAD_OR_INCAP(living_pawn))
			return FALSE
	return TRUE

/datum/ai_controller/basic_controller/proc/update_speed(mob/living/basic/basic_mob)
	SIGNAL_HANDLER
	movement_delay = basic_mob.cached_multiplicative_slowdown


code:
/datum/ai_behavior/basic_melee_attack
	action_cooldown = 0.6 SECONDS
	behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT

/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
	. = ..()
	controller.current_movement_target =  controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority

/datum/ai_behavior/basic_melee_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
	. = ..()
	var/mob/living/basic/basic_mob = controller.pawn
	var/atom/target = controller.blackboard[target_key]
	var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]

	if(!targetting_datum.can_attack(basic_mob, target))
		finish_action(controller, FALSE, target_key)
		return

	var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something!

	controller.blackboard[hiding_location_key] = hiding_target

	if(hiding_target) //Slap it!
		basic_mob.melee_attack(hiding_target)
	else
		basic_mob.melee_attack(target)

/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key)
	. = ..()
	if(!succeeded)
		controller.blackboard -= target_key

/datum/ai_behavior/basic_ranged_attack
	action_cooldown = 0.6 SECONDS
	behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
	required_distance = 3

/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
	. = ..()
	controller.current_movement_target =  controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority

/datum/ai_behavior/basic_ranged_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
	. = ..()
	var/mob/living/basic/basic_mob = controller.pawn
	var/atom/target = controller.blackboard[target_key]
	var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]

	if(!targetting_datum.can_attack(basic_mob, target))
		finish_action(controller, FALSE, target_key)
		return

	var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something!

	controller.blackboard[hiding_location_key] = hiding_target

	if(hiding_target) //Shoot it!
		basic_mob.RangedAttack(hiding_target)
	else
		basic_mob.RangedAttack(target)

/datum/ai_behavior/basic_ranged_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key)
	. = ..()
	if(!succeeded)
		controller.blackboard -= target_key


code:
/datum/ai_behavior/find_potential_targets
	action_cooldown = 2 SECONDS
	var/vision_range = 9
	///List of potentially dangerous objs
	var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha))

/datum/ai_behavior/find_potential_targets/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
	. = ..()
	var/list/potential_targets
	var/mob/living/living_mob = controller.pawn
	var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]

	if(!targetting_datum)
		CRASH("No target datum was supplied in the blackboard for [controller.pawn]")

	potential_targets = hearers(vision_range, controller.pawn) - living_mob //Remove self, so we don't suicide

	for(var/HM in typecache_filter_list(range(vision_range, living_mob), hostile_machines)) //Can we see any hostile machines?
		if(can_see(living_mob, HM, vision_range))
			potential_targets += HM

	if(!potential_targets.len)
		finish_action(controller, FALSE)
		return

	var/list/filtered_targets = list()

	for(var/atom/pot_target in potential_targets)
		if(targetting_datum.can_attack(living_mob, pot_target))//Can we attack it?
			filtered_targets += pot_target
			continue

	if(!filtered_targets.len)
		finish_action(controller, FALSE)
		return

	var/atom/target = pick(filtered_targets)
	controller.blackboard[target_key] = target

	var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, target)

	if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially.
		controller.blackboard[hiding_location_key] = potential_hiding_location

	finish_action(controller, TRUE)


code:
///type of tipped reaction that is akin to puppy dog eyes
/datum/ai_behavior/tipped_reaction

/datum/ai_behavior/tipped_reaction/perform(delta_time, datum/ai_controller/controller, tipper_key, reacting_key)
	. = ..()

	var/mob/living/carbon/tipper = controller.blackboard[tipper_key]

	// visible part of the visible message
	var/seen_message = ""
	// self part of the visible message
	var/self_message = ""
	// the mob we're looking to for aid
	var/mob/living/carbon/savior
	// look for someone in a radius around us for help. If our original tipper is in range, prioritize them
	for(var/mob/living/carbon/potential_aid in oview(3, get_turf(controller.pawn)))
		if(potential_aid == tipper)
			savior = tipper
			break
		savior = potential_aid

	if(prob(75) && savior)
		var/text = pick("imploringly", "pleadingly", "with a resigned expression")
		seen_message = "[controller.pawn] looks at [savior] [text]."
		self_message = "You look at [savior] [text]."
	else
		seen_message = "[controller.pawn] seems resigned to its fate."
		self_message = "You resign yourself to your fate."
	controller.pawn.visible_message(span_notice("[seen_message]"), span_notice("[self_message]"))
	finish_action(controller, TRUE, tipper_key, reacting_key)

/datum/ai_behavior/tipped_reaction/finish_action(datum/ai_controller/controller, succeeded, tipper_key, reacting_key)
	. = ..()
	//I'VE SAID MY PEACE...
	controller.blackboard[reacting_key] = FALSE
	controller.blackboard[tipper_key] = null


code:
/datum/ai_planning_subtree/basic_melee_attack_subtree
	var/datum/ai_behavior/basic_melee_attack/melee_attack_behavior = /datum/ai_behavior/basic_melee_attack

/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time)
	. = ..()
	var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
	if(!target || QDELETED(target))
		return
	controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
	return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.

//If you give this to something without the element you are a dumbass.
/datum/ai_planning_subtree/basic_ranged_attack_subtree
	var/datum/ai_behavior/basic_ranged_attack/ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack

/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time)
	. = ..()
	var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
	if(!target || QDELETED(target))
		return
	controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
	return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.


code:
/datum/ai_planning_subtree/simple_find_target

/datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, delta_time)
	. = ..()
	var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
	if(target && !QDELETED(target))
		return
	controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)


code:
/datum/ai_planning_subtree/random_speech
	//The chance of an emote occuring each second
	var/speech_chance = 0
	///Hearable emotes
	var/list/emote_hear = list()
	///Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps
	var/list/emote_see = list()
	///Possible lines of speech the AI can have
	var/list/speak = list()

/datum/ai_planning_subtree/random_speech/New()
	. = ..()
	if(speak)
		speak = string_list(speak)
	if(emote_hear)
		emote_hear = string_list(emote_hear)
	if(emote_see)
		emote_see = string_list(emote_see)

/datum/ai_planning_subtree/random_speech/SelectBehaviors(datum/ai_controller/controller, delta_time)
	if(DT_PROB(speech_chance, delta_time))
		var/audible_emotes_length = emote_hear?.len
		var/non_audible_emotes_length = emote_see?.len
		var/speak_lines_length = speak?.len

		var/total_choices_length = audible_emotes_length + non_audible_emotes_length + speak_lines_length

		var/random_number_in_range =  rand(1, total_choices_length)

		if(random_number_in_range <= audible_emotes_length)
			controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_hear))
		else if(random_number_in_range <= (audible_emotes_length + non_audible_emotes_length))
			controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_see))
		else
			controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(speak))

/datum/ai_planning_subtree/random_speech/cockroach
	speech_chance = 5
	emote_hear = list("chitters")

/datum/ai_planning_subtree/random_speech/cow
	speech_chance = 1
	speak = list("moo?","moo","MOOOOOO")
	emote_hear = list("brays.")
	emote_see = list("shakes her head.")

///unlike normal cows, wisdom cows speak of wisdom and won't shut the fuck up
/datum/ai_planning_subtree/random_speech/cow/wisdom
	speech_chance = 15

/datum/ai_planning_subtree/random_speech/cow/wisdom/New()
	. = ..()
	speak = GLOB.wisdoms //Done here so it's setup properly


code:
///used by cows
/datum/ai_planning_subtree/tip_reaction

/datum/ai_planning_subtree/tip_reaction/SelectBehaviors(datum/ai_controller/controller, delta_time)
	. = ..()
	var/tip_reacting = controller.blackboard[BB_BASIC_MOB_TIP_REACTING]
	if(!tip_reacting)
		return
	controller.queue_behavior(/datum/ai_behavior/tipped_reaction, BB_BASIC_MOB_TIPPER, BB_BASIC_MOB_TIP_REACTING)
	return SUBTREE_RETURN_FINISH_PLANNING //no point in trying, boy. you're TIPPED.


code:
///Datum for basic mobs to define what they can attack.
/datum/targetting_datum

///Returns true or false depending on if the target can be attacked by the mob
/datum/targetting_datum/proc/can_attack(mob/living/living_mob, atom/target)
	return

///Returns something the target might be hiding inside of
/datum/targetting_datum/proc/find_hidden_mobs(mob/living/living_mob, atom/target)
	var/atom/target_hiding_location
	if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper))
		target_hiding_location = target.loc
	return target_hiding_location

/datum/targetting_datum/basic

/datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target)
	if(isturf(the_target) || !the_target) // bail out on invalids
		return FALSE

	if(ismob(the_target)) //Target is in godmode, ignore it.
		var/mob/M = the_target
		if(M.status_flags & GODMODE)
			return FALSE

	if(living_mob.see_invisible < the_target.invisibility)//Target's invisible to us, forget it
		return FALSE

	if(living_mob.z != the_target.z)
		return FALSE

	if(isliving(the_target)) //Targetting vs living mobs
		var/mob/living/L = the_target
		var/faction_check = living_mob.faction_check_mob(L)
		if(faction_check || L.stat)
			return FALSE
		return TRUE

	if(ismecha(the_target)) //Targetting vs mechas
		var/obj/vehicle/sealed/mecha/M = the_target
		for(var/occupant in M.occupants)
			if(can_attack(living_mob, occupant)) //Can we attack any of the occupants?
				return TRUE

	if(istype(the_target, /obj/machinery/porta_turret)) //Cringe turret! kill it!
		var/obj/machinery/porta_turret/P = the_target
		if(P.in_faction(living_mob)) //Don't attack if the turret is in the same faction
			return FALSE
		if(P.has_cover && !P.raised) //Don't attack invincible turrets
			return FALSE
		if(P.machine_stat & BROKEN) //Or turrets that are already broken
			return FALSE
		return TRUE

	return FALSE


code:
/datum/ai_behavior/item_move_close_and_attack/cursed
	attack_sound = 'sound/items/haunted/ghostitemattack.ogg'
	max_attempts = 4

/datum/ai_behavior/item_move_close_and_attack/cursed/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key)
	var/atom/throw_target = controller.blackboard[target_key]
	//dropping our target from the blackboard if they are no longer a valid target after the attack behavior
	if(get_dist(throw_target, controller.pawn) > CURSED_VIEW_RANGE)
		controller.blackboard[target_key] = null


code:
/**
 * # cursed item ai!
 *
 * Haunted AI tries to not be interacted with, and will attack people who do.
 * Cursed AI instead tries to be interacted with, and will attempt to equip itself onto people.
 * Added by /datum/element/cursed, and as such will try to remove this element and go dormant when it finds a victim to curse
 */
/datum/ai_controller/cursed
	movement_delay = 0.4 SECONDS
	blackboard = list(
		BB_CURSE_TARGET,
		BB_TARGET_SLOT,
		BB_CURSED_THROW_ATTEMPT_COUNT
	)
	planning_subtrees = list(/datum/ai_planning_subtree/cursed)
	idle_behavior = /datum/idle_behavior/idle_ghost_item

/datum/ai_controller/cursed/TryPossessPawn(atom/new_pawn)
	if(!isitem(new_pawn))
		return AI_CONTROLLER_INCOMPATIBLE
	RegisterSignal(new_pawn, COMSIG_MOVABLE_IMPACT, .proc/on_throw_hit)
	RegisterSignal(new_pawn, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
	return ..() //Run parent at end

/datum/ai_controller/cursed/UnpossessPawn()
	UnregisterSignal(pawn, list(COMSIG_MOVABLE_IMPACT, COMSIG_ITEM_EQUIPPED))
	return ..() //Run parent at end

///signal called by the pawn hitting something after a throw
/datum/ai_controller/cursed/proc/on_throw_hit(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum)
	SIGNAL_HANDLER
	if(!iscarbon(hit_atom))
		return
	//equipcode has sleeps all over it.
	INVOKE_ASYNC(src, .proc/try_equipping_to_target_slot, hit_atom)

///signal called by picking up the pawn, will try to equip to where it should actually be and start the curse
/datum/ai_controller/cursed/proc/on_equip(datum/source, mob/equipper, slot)
	SIGNAL_HANDLER
	INVOKE_ASYNC(src, .proc/try_equipping_to_target_slot, equipper, slot)

/**
 * curse of hunger component; for very hungry items.
 *
 * called when someone grabs the cursed item or the cursed item impacts with a mob it's throwing itself at
 * arguments:
 * * curse_victim: whomever we're attaching this to
 * * slot_already_in: the slot the item is already in before this was called, possibly null but at least in hands if picked up
 */
/datum/ai_controller/cursed/proc/try_equipping_to_target_slot(mob/living/carbon/curse_victim, slot_already_in)
	var/obj/item/item_pawn = pawn
	var/attempted_slot = blackboard[BB_TARGET_SLOT]
	if(slot_already_in && attempted_slot == slot_already_in) //thanks for making it easy
		what_a_horrible_night_to_have_a_curse()
		return
	if(attempted_slot == ITEM_SLOT_HANDS) //hands needs some different checks
		curse_victim.drop_all_held_items()
		if(curse_victim.put_in_hands(item_pawn, del_on_fail = FALSE))
			to_chat(curse_victim, span_danger("[item_pawn] leaps into your hands!"))
			what_a_horrible_night_to_have_a_curse()
		return
	var/obj/item/blocking = curse_victim.get_item_by_slot(attempted_slot)
	if(!curse_victim.dropItemToGround(blocking, silent = TRUE))
		return //cannot equip to this person so whatever just keep whacking them until they die or fugg off
	curse_victim.equip_to_slot_if_possible(item_pawn, attempted_slot, qdel_on_fail = FALSE, disable_warning = FALSE)
	to_chat(curse_victim, span_danger("[item_pawn] equips [item_pawn.p_them()]self onto you!"))
	what_a_horrible_night_to_have_a_curse()

///proc called when the cursed object successfully attaches itself to someone, removing the cursed element and by extension the ai itself
/datum/ai_controller/cursed/proc/what_a_horrible_night_to_have_a_curse()
	var/obj/item/item_pawn = pawn
	item_pawn.RemoveElement(/datum/element/cursed)


code:
/datum/ai_planning_subtree/cursed/SelectBehaviors(datum/ai_controller/controller, delta_time)
	var/obj/item/item_pawn = controller.pawn

	//make sure we have a target
	var/mob/living/carbon/curse_target = controller.blackboard[BB_CURSE_TARGET]
	if(!curse_target)
		controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_CURSE_TARGET, /mob/living/carbon, CURSED_VIEW_RANGE)
		return
	//make sure attack is valid
	if(get_dist(curse_target, item_pawn) > CURSED_VIEW_RANGE)
		controller.blackboard[BB_CURSE_TARGET] = null
		return
	controller.current_movement_target = curse_target
	controller.queue_behavior(/datum/ai_behavior/item_move_close_and_attack/cursed)


code:
/datum/ai_behavior/battle_screech/dog
	screeches = list("barks","howls")

/// Fetching makes the pawn chase after whatever it's targeting and pick it up when it's in range, with the dog_equip behavior
/datum/ai_behavior/fetch
	behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT

/datum/ai_behavior/fetch/perform(delta_time, datum/ai_controller/controller)
	. = ..()
	var/mob/living/living_pawn = controller.pawn
	var/obj/item/fetch_thing = controller.blackboard[BB_FETCH_TARGET]

	//either we can't pick it up, or we'd rather eat it, so stop trying.
	if(fetch_thing.anchored || !isturf(fetch_thing.loc) || IS_EDIBLE(fetch_thing) || !living_pawn.CanReach(fetch_thing))
		finish_action(controller, FALSE)
		return

	finish_action(controller, TRUE)

/datum/ai_behavior/fetch/finish_action(datum/ai_controller/controller, success)
	. = ..()

	if(!success) //Don't try again on this item if we failed
		var/obj/item/target = controller.blackboard[BB_FETCH_TARGET]
		if(target)
			controller.blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(target)] = TRUE
		controller.blackboard[BB_FETCH_TARGET] = null
		controller.blackboard[BB_FETCH_DELIVER_TO] = null

/// This is simply a behaviour to pick up a fetch target
/datum/ai_behavior/simple_equip/perform(delta_time, datum/ai_controller/controller)
	. = ..()
	var/obj/item/fetch_target = controller.blackboard[BB_FETCH_TARGET]
	if(!isturf(fetch_target?.loc) || !isitem(fetch_target)) // someone picked it up, something happened to it, or it wasn't an item anyway
		finish_action(controller, FALSE)
		return

	if(in_range(controller.pawn, fetch_target))
		pickup_item(controller, fetch_target)
		finish_action(controller, TRUE)
	else
		finish_action(controller, FALSE)

/datum/ai_behavior/simple_equip/finish_action(datum/ai_controller/controller, success)
	. = ..()
	controller.blackboard[BB_FETCH_TARGET] = null

/datum/ai_behavior/simple_equip/proc/pickup_item(datum/ai_controller/controller, obj/item/target)
	var/atom/pawn = controller.pawn
	drop_item(controller)
	pawn.visible_message(span_notice("[pawn] picks up [target] in [pawn.p_their()] mouth."))
	target.forceMove(pawn)
	controller.blackboard[BB_SIMPLE_CARRY_ITEM] = target
	return TRUE

/datum/ai_behavior/simple_equip/proc/drop_item(datum/ai_controller/controller)
	var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
	if(!carried_item)
		return

	var/atom/pawn = controller.pawn
	pawn.visible_message(span_notice("[pawn] drops [carried_item]."))
	carried_item.forceMove(get_turf(pawn))
	controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null
	return TRUE

/// This behavior involves dropping off a carried item to a specified person (or place)
/datum/ai_behavior/deliver_item
	behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT

/datum/ai_behavior/deliver_item/perform(delta_time, datum/ai_controller/controller)
	. = ..()
	var/mob/living/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
	if(!return_target)
		finish_action(controller, FALSE)
	if(in_range(controller.pawn, return_target))
		deliver_item(controller)
		finish_action(controller, TRUE)

/datum/ai_behavior/deliver_item/finish_action(datum/ai_controller/controller, success)
	. = ..()
	controller.blackboard[BB_FETCH_DELIVER_TO] = null

/// Actually drop the fetched item to the target
/datum/ai_behavior/deliver_item/proc/deliver_item(datum/ai_controller/controller)
	var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
	var/atom/movable/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
	if(!carried_item || !return_target)
		finish_action(controller, FALSE)
		return

	if(ismob(return_target))
		controller.pawn.visible_message(span_notice("[controller.pawn] delivers [carried_item] at [return_target]'s feet."))
	else // not sure how to best phrase this
		controller.pawn.visible_message(span_notice("[controller.pawn] delivers [carried_item] to [return_target]."))

	carried_item.forceMove(get_turf(return_target))
	controller.blackboard -= BB_SIMPLE_CARRY_ITEM
	return TRUE

/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
/datum/ai_behavior/eat_snack
	behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT

/datum/ai_behavior/eat_snack/perform(delta_time, datum/ai_controller/controller)
	. = ..()
	var/obj/item/snack = controller.current_movement_target
	if(!istype(snack) || !IS_EDIBLE(snack) || !(isturf(snack.loc) || ishuman(snack.loc)))
		finish_action(controller, FALSE)

	var/mob/living/living_pawn = controller.pawn
	if(!in_range(living_pawn, snack))
		return

	if(isturf(snack.loc))
		snack.attack_animal(living_pawn) // snack attack!
	else if(iscarbon(snack.loc) && DT_PROB(10, delta_time))
		living_pawn.manual_emote("stares at [snack.loc]'s [snack.name] with a sad puppy-face.")

	if(QDELETED(snack)) // we ate it!
		finish_action(controller, TRUE)

/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
/datum/ai_behavior/play_dead
	behavior_flags = NONE

/datum/ai_behavior/play_dead/perform(delta_time, datum/ai_controller/controller)
	. = ..()
	var/mob/living/simple_animal/simple_pawn = controller.pawn
	if(!istype(simple_pawn))
		return

	if(!controller.blackboard[BB_DOG_PLAYING_DEAD])
		controller.blackboard[BB_DOG_PLAYING_DEAD] = TRUE
		simple_pawn.emote("deathgasp", intentional=FALSE)
		simple_pawn.icon_state = simple_pawn.icon_dead
		if(simple_pawn.flip_on_death)
			simple_pawn.transform = simple_pawn.transform.Turn(180)
		simple_pawn.set_density(FALSE)

	if(DT_PROB(10, delta_time))
		finish_action(controller, TRUE)

/datum/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded)
	. = ..()
	var/mob/living/simple_animal/simple_pawn = controller.pawn
	if(!istype(simple_pawn) || simple_pawn.stat) // imagine actually dying while playing dead. hell, imagine being the kid waiting for your pup to get back up :(
		return
	controller.blackboard[BB_DOG_PLAYING_DEAD] = FALSE
	simple_pawn.visible_message(span_notice("[simple_pawn] springs to [simple_pawn.p_their()] feet, panting excitedly!"))
	simple_pawn.icon_state = simple_pawn.icon_living
	if(simple_pawn.flip_on_death)
		simple_pawn.transform = simple_pawn.transform.Turn(180)
	simple_pawn.set_density(initial(simple_pawn.density))

/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
/datum/ai_behavior/harass
	behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
	required_distance = 3

/datum/ai_behavior/harass/perform(delta_time, datum/ai_controller/controller)
	. = ..()
	var/mob/living/living_pawn = controller.pawn
	if(!istype(living_pawn) || !(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK)))
		return

	var/datum/weakref/harass_ref = controller.blackboard[BB_DOG_HARASS_TARGET]
	var/atom/movable/harass_target = harass_ref.resolve()
	if(!harass_target || !can_see(living_pawn, harass_target, length=AI_DOG_VISION_RANGE))
		finish_action(controller, FALSE)
		return

	if(controller.blackboard[BB_DOG_FRIENDS][harass_ref])
		living_pawn.visible_message(span_danger("[living_pawn] looks sideways at [harass_target] for a moment, then shakes [living_pawn.p_their()] head and ceases aggression."))
		finish_action(controller, FALSE)
		return

	var/mob/living/living_target = harass_target
	if(istype(living_target) && (living_target.stat || HAS_TRAIT(living_target, TRAIT_FAKEDEATH)))
		finish_action(controller, TRUE)
		return

	// subtypes of this behavior can change behavior for how eager/averse the pawn is to attack the target as opposed to falling back/making noise/getting help
	if(in_range(living_pawn, living_target))
		attack(controller, living_target)
	else if(DT_PROB(50, delta_time))
		living_pawn.manual_emote("[pick("barks", "growls", "stares")] menacingly at [harass_target]!")

/datum/ai_behavior/harass/finish_action(datum/ai_controller/controller, succeeded)
	. = ..()
	controller.blackboard[BB_DOG_HARASS_TARGET] = null

/// A proc representing when the mob is pushed to actually attack the target. Again, subtypes can be used to represent different attacks from different animals, or it can be some other generic behavior
/datum/ai_behavior/harass/proc/attack(datum/ai_controller/controller, mob/living/living_target)
	var/mob/living/living_pawn = controller.pawn
	if(!istype(living_pawn))
		return
	// make sure the pawn gets some temporary strength boost to actually attack the target instead of pathetically nuzzling them.
	var/old_melee_lower = living_pawn.melee_damage_lower
	var/old_melee_upper = living_pawn.melee_damage_upper
	living_pawn.melee_damage_lower = max(5, old_melee_lower)
	living_pawn.melee_damage_upper = max(10, old_melee_upper)

	living_pawn.UnarmedAttack(living_target, FALSE)

	living_pawn.melee_damage_lower = old_melee_lower
	living_pawn.melee_damage_upper = old_melee_upper