diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm
index 06c0fa35cf5..db0a2f31547 100644
--- a/code/__DEFINES/ai.dm
+++ b/code/__DEFINES/ai.dm
@@ -21,9 +21,11 @@
#define AI_BEHAVIOR_MOVE_AND_PERFORM (1<<1)
-///Monkey AI controller blackboard keys
+// Monkey AI controller blackboard keys
#define BB_MONKEY_AGRESSIVE "BB_monkey_agressive"
+#define BB_MONKEY_GUN_NEURONS_ACTIVATED "BB_monkey_gun_aware"
+#define BB_MONKEY_GUN_WORKED "BB_monkey_gun_worked"
#define BB_MONKEY_BEST_FORCE_FOUND "BB_monkey_bestforcefound"
#define BB_MONKEY_ENEMIES "BB_monkey_enemies"
#define BB_MONKEY_BLACKLISTITEMS "BB_monkey_blacklistitems"
@@ -33,6 +35,7 @@
#define BB_MONKEY_TARGET_DISPOSAL "BB_monkey_target_disposal"
#define BB_MONKEY_DISPOSING "BB_monkey_disposing"
#define BB_MONKEY_RECRUIT_COOLDOWN "BB_monkey_recruit_cooldown"
+#define BB_MONKEY_NEXT_HUNGRY "BB_monkey_next_hungry"
///Haunted item controller defines
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 56514e04034..9c6996e8eb7 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -140,6 +140,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_PACIFISM "pacifism"
#define TRAIT_IGNORESLOWDOWN "ignoreslow"
#define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown"
+/// Makes it so the mob can use guns regardless of tool user status
+#define TRAIT_GUN_NATURAL "gunnatural"
/// Causes death-like unconsciousness
#define TRAIT_DEATHCOMA "deathcoma"
/// Makes the owner appear as dead to most forms of medical examination
diff --git a/code/datums/ai/_ai_behavior.dm b/code/datums/ai/_ai_behavior.dm
index b22b8e8014f..0ce84505a1f 100644
--- a/code/datums/ai/_ai_behavior.dm
+++ b/code/datums/ai/_ai_behavior.dm
@@ -7,15 +7,20 @@
///Cooldown between actions performances
var/action_cooldown = 0
+/// 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)
+/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.
/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded)
controller.current_behaviors.Remove(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)
- return
diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm
index 98fb3173a83..f68b8c2f79a 100644
--- a/code/datums/ai/_ai_controller.dm
+++ b/code/datums/ai/_ai_controller.dm
@@ -18,6 +18,8 @@ have ways of interacting with a specific atom and control it. They posses a blac
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?
@@ -34,6 +36,8 @@ have ways of interacting with a specific atom and control it. They posses a blac
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)
ai_movement = SSai_movement.movement_types[ai_movement]
@@ -85,6 +89,8 @@ have ways of interacting with a specific atom and control it. They posses a blac
///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
/// Generates a plan and see if our existing one is still valid.
@@ -92,13 +98,13 @@ have ways of interacting with a specific atom and control it. They posses a blac
if(!able_to_run())
walk(pawn, 0) //stop moving
return //this should remove them from processing in the future through event-based stuff.
+
if(!current_behaviors?.len)
SelectBehaviors(delta_time)
if(!current_behaviors?.len)
PerformIdleBehavior(delta_time) //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
@@ -118,20 +124,19 @@ have ways of interacting with a specific atom and control it. They posses a blac
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.else
ai_movement.stop_moving_towards(src)
- current_behavior.perform(action_delta_time, src)
+ 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.
- current_behavior.perform(action_delta_time, src)
+ ProcessBehavior(action_delta_time, current_behavior)
return
else //No movement required
- current_behavior.perform(action_delta_time, src)
+ ProcessBehavior(action_delta_time, current_behavior)
return
-
///Perform some dumb idle behavior.
/datum/ai_controller/proc/PerformIdleBehavior(delta_time)
return
@@ -154,6 +159,29 @@ have ways of interacting with a specific atom and control it. They posses a blac
STOP_PROCESSING(SSai_controllers, src)
CancelActions()
+/datum/ai_controller/proc/PauseAi(time)
+ paused_until = world.time + time
+
+/datum/ai_controller/proc/AddBehavior(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
+ current_behaviors += behavior
+ arguments.Cut(1, 2)
+ if(length(arguments))
+ behavior_args[behavior_type] = arguments
+
+/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()
for(var/i in current_behaviors)
var/datum/ai_behavior/current_behavior = i
diff --git a/code/datums/ai/generic_actions.dm b/code/datums/ai/generic_actions.dm
index d148a1578bd..038ca326059 100644
--- a/code/datums/ai/generic_actions.dm
+++ b/code/datums/ai/generic_actions.dm
@@ -61,3 +61,98 @@
if(succeeded)
controller.blackboard[target_key] = null
return ..()
+
+/// Use in hand the currently held item
+/datum/ai_behavior/use_in_hand
+ behavior_flags = AI_BEHAVIOR_MOVE_AND_PERFORM
+
+/datum/ai_behavior/use_in_hand/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+ var/obj/item/held = pawn.get_item_by_slot(pawn.get_active_hand())
+ if(!held)
+ finish_action(controller, FALSE)
+ return
+ pawn.activate_hand(pawn.get_active_hand())
+ finish_action(controller, TRUE)
+
+/// Use the currently held item, or unarmed, on an object in the world
+/datum/ai_behavior/use_on_object
+ required_distance = 1
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/use_on_object/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+ var/obj/item/held_item = pawn.get_item_by_slot(pawn.get_active_hand())
+ var/atom/target = controller.current_movement_target
+
+ if(!target || !pawn.CanReach(target))
+ finish_action(controller, FALSE)
+ return
+
+ pawn.set_combat_mode(FALSE)
+ if(held_item)
+ held_item.melee_attack_chain(pawn, target)
+ else
+ pawn.UnarmedAttack(target, TRUE)
+
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/give
+ required_distance = 1
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/give/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+ var/obj/item/held_item = pawn.get_item_by_slot(pawn.get_active_hand())
+ var/atom/target = controller.current_movement_target
+
+ if(!target || !pawn.CanReach(target) || !isliving(target))
+ finish_action(controller, FALSE)
+ return
+
+ var/mob/living/living_target = target
+ controller.PauseAi(1.5 SECONDS)
+ living_target.visible_message(
+ "[pawn] starts trying to give [held_item] to [living_target]!",
+ "[pawn] tries to give you [held_item]!"
+ )
+ if(!do_mob(pawn, living_target, 1 SECONDS))
+ return
+ if(QDELETED(held_item) || QDELETED(living_target))
+ finish_action(controller, FALSE)
+ return
+ var/pocket_choice = prob(50) ? ITEM_SLOT_RPOCKET : ITEM_SLOT_LPOCKET
+ if(prob(50) && living_target.can_put_in_hand(held_item))
+ living_target.put_in_hand(held_item)
+ else if(held_item.mob_can_equip(living_target, pawn, pocket_choice, TRUE))
+ living_target.equip_to_slot(held_item, pocket_choice)
+
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/consume
+ required_distance = 1
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+ action_cooldown = 2 SECONDS
+
+/datum/ai_behavior/consume/setup(datum/ai_controller/controller, obj/item/target)
+ . = ..()
+ controller.current_movement_target = target
+
+/datum/ai_behavior/consume/perform(delta_time, datum/ai_controller/controller, obj/item/target)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+
+ if(!(target in pawn.held_items))
+ if(!pawn.put_in_hand_check(target))
+ finish_action(controller, FALSE)
+ return
+
+ pawn.put_in_hands(target)
+
+ target.melee_attack_chain(pawn, pawn)
+
+ if(QDELETED(target) || prob(10)) // Even if we don't finish it all we can randomly decide to be done
+ finish_action(controller, TRUE)
diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm
index c7d10a53403..18ff717108a 100644
--- a/code/datums/ai/monkey/monkey_behaviors.dm
+++ b/code/datums/ai/monkey/monkey_behaviors.dm
@@ -58,8 +58,6 @@
finish_action(controller, TRUE)
return
-
-
finish_action(controller, FALSE)
/datum/ai_behavior/monkey_equip/ground
@@ -145,7 +143,7 @@
if(!target || target.stat != CONSCIOUS)
finish_action(controller, TRUE) //Target == owned
- if(living_pawn.Adjacent(target) && isturf(target.loc) && !IS_DEAD_OR_INCAP(living_pawn)) // if right next to perp
+ if(isturf(target.loc) && !IS_DEAD_OR_INCAP(living_pawn)) // Check if they're a valid target
// check if target has a weapon
var/obj/item/W
for(var/obj/item/I in target.held_items)
@@ -168,7 +166,6 @@
/// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little
/datum/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, delta_time, disarm)
-
var/mob/living/living_pawn = controller.pawn
if(living_pawn.next_move > world.time)
@@ -182,11 +179,33 @@
living_pawn.set_combat_mode(TRUE)
+ if(isnull(controller.blackboard[BB_MONKEY_GUN_WORKED]))
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = TRUE
+
// attack with weapon if we have one
- if(weapon)
- weapon.melee_attack_chain(living_pawn, target)
- else
- living_pawn.UnarmedAttack(target, null, disarm ? list("right" = TRUE) : null) //Fake a right click if we're disarming
+ if(living_pawn.CanReach(target, weapon))
+ if(weapon)
+ weapon.melee_attack_chain(living_pawn, target)
+ else
+ living_pawn.UnarmedAttack(target, null, disarm ? list("right" = TRUE) : null) //Fake a right click if we're disarmin
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = TRUE // We reset their memory of the gun being 'broken' if they accomplish some other attack
+ else if(weapon)
+ var/atom/real_target = target
+ if(prob(10)) // Artificial miss
+ real_target = pick(oview(2, target))
+
+ var/obj/item/gun/gun = locate() in living_pawn.held_items
+ var/can_shoot = gun?.can_shoot() || FALSE
+ if(gun && controller.blackboard[BB_MONKEY_GUN_WORKED] && prob(95))
+ // We attempt to attack even if we can't shoot so we get the effects of pulling the trigger
+ gun.afterattack(real_target, living_pawn, FALSE)
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = can_shoot ? TRUE : prob(80) // Only 20% likely to notice it didn't work
+ if(can_shoot)
+ controller.blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] = TRUE
+ else
+ living_pawn.throw_item(real_target)
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = TRUE // 'worked'
+
// no de-aggro
if(controller.blackboard[BB_MONKEY_AGRESSIVE])
return
diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm
index 77ab2fac0af..7a42cc3d348 100644
--- a/code/datums/ai/monkey/monkey_controller.dm
+++ b/code/datums/ai/monkey/monkey_controller.dm
@@ -6,16 +6,20 @@ have ways of interacting with a specific mob and control it.
/datum/ai_controller/monkey
movement_delay = 0.4 SECONDS
- blackboard = list(BB_MONKEY_AGRESSIVE = FALSE,\
- BB_MONKEY_BEST_FORCE_FOUND = 0,\
- BB_MONKEY_ENEMIES = list(),\
- BB_MONKEY_BLACKLISTITEMS = list(),\
- BB_MONKEY_PICKUPTARGET = null,\
- BB_MONKEY_PICKPOCKETING = FALSE,
- BB_MONKEY_DISPOSING = FALSE,
- BB_MONKEY_TARGET_DISPOSAL = null,
- BB_MONKEY_CURRENT_ATTACK_TARGET = null,
- BB_MONKEY_CURRENT_ATTACK_TARGET)
+ blackboard = list(
+ BB_MONKEY_AGRESSIVE = FALSE,
+ BB_MONKEY_BEST_FORCE_FOUND = 0,
+ BB_MONKEY_ENEMIES = list(),
+ BB_MONKEY_BLACKLISTITEMS = list(),
+ BB_MONKEY_PICKUPTARGET = null,
+ BB_MONKEY_PICKPOCKETING = FALSE,
+ BB_MONKEY_DISPOSING = FALSE,
+ BB_MONKEY_TARGET_DISPOSAL = null,
+ BB_MONKEY_CURRENT_ATTACK_TARGET = null,
+ BB_MONKEY_GUN_NEURONS_ACTIVATED = FALSE,
+ BB_MONKEY_GUN_WORKED = TRUE,
+ BB_MONKEY_NEXT_HUNGRY = 0
+ )
/datum/ai_controller/monkey/angry
@@ -28,6 +32,9 @@ have ways of interacting with a specific mob and control it.
/datum/ai_controller/monkey/TryPossessPawn(atom/new_pawn)
if(!isliving(new_pawn))
return AI_CONTROLLER_INCOMPATIBLE
+
+ blackboard[BB_MONKEY_NEXT_HUNGRY] = world.time + rand(0, 300)
+
var/mob/living/living_pawn = new_pawn
RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, .proc/on_attackby)
RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
@@ -40,6 +47,7 @@ have ways of interacting with a specific mob and control it.
RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, .proc/on_attack_hulk)
RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, .proc/on_attempt_cuff)
RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, .proc/update_movespeed)
+ RegisterSignal(new_pawn, COMSIG_FOOD_EATEN, .proc/on_eat)
movement_delay = living_pawn.cached_multiplicative_slowdown
return ..() //Run parent at end
@@ -49,11 +57,11 @@ have ways of interacting with a specific mob and control it.
return ..() //Run parent at end
/datum/ai_controller/monkey/able_to_run()
+ . = ..()
var/mob/living/living_pawn = pawn
if(IS_DEAD_OR_INCAP(living_pawn))
return FALSE
- return ..()
/datum/ai_controller/monkey/SelectBehaviors(delta_time)
current_behaviors = list()
@@ -67,16 +75,18 @@ have ways of interacting with a specific mob and control it.
if(HAS_TRAIT(pawn, TRAIT_PACIFISM)) //Not a pacifist? lets try some combat behavior.
return
+
+ var/mob/living/selected_enemy
if(length(enemies) || blackboard[BB_MONKEY_AGRESSIVE]) //We have enemies or are pissed
-
- var/mob/living/selected_enemy
-
+ var/list/valids = list()
for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, living_pawn))
if(possible_enemy == living_pawn || (!enemies[possible_enemy] && (!blackboard[BB_MONKEY_AGRESSIVE] || HAS_AI_CONTROLLER_TYPE(possible_enemy, /datum/ai_controller/monkey)))) //Are they an enemy? (And do we even care?)
continue
+ // Weighted list, so the closer they are the more likely they are to be chosen as the enemy
+ valids[possible_enemy] = CEILING(100 / (get_dist(living_pawn, possible_enemy) || 1), 1)
+
+ selected_enemy = pickweight(valids)
- selected_enemy = possible_enemy
- break
if(selected_enemy)
if(!selected_enemy.stat) //He's up, get him!
if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle
@@ -103,12 +113,42 @@ have ways of interacting with a specific mob and control it.
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/disposal_mob)
return
- return //Too busy fighting to steal atm.
+ if(prob(5))
+ current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/use_in_hand)
- else if(DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time))
- if(TryFindWeapon()) //Found a better weapon, let's grab it first.
+ if(selected_enemy || !DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time))
+ return
+
+ if(world.time >= blackboard[BB_MONKEY_NEXT_HUNGRY] && TryFindFood())
+ return
+
+ if(prob(50))
+ var/list/possible_targets = list()
+ for(var/atom/thing in view(2, living_pawn))
+ if(!thing.mouse_opacity)
+ continue
+ if(thing.IsObscured())
+ continue
+ possible_targets += thing
+ var/atom/target = pick(possible_targets)
+ if(target)
+ current_movement_target = target
+ current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/use_on_object)
return
+ if(prob(5) && (locate(/obj/item) in living_pawn.held_items))
+ var/list/possible_receivers = list()
+ for(var/mob/living/candidate in oview(2, pawn))
+ possible_receivers += candidate
+
+ if(length(possible_receivers))
+ var/mob/living/target = pick(possible_receivers)
+ current_movement_target = target
+ current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/give)
+ return
+
+ TryFindWeapon()
+
///re-used behavior pattern by monkeys for finding a weapon
/datum/ai_controller/monkey/proc/TryFindWeapon()
var/mob/living/living_pawn = pawn
@@ -116,29 +156,103 @@ have ways of interacting with a specific mob and control it.
if(!locate(/obj/item) in living_pawn.held_items)
blackboard[BB_MONKEY_BEST_FORCE_FOUND] = 0
- var/obj/item/W
- for(var/obj/item/i in oview(2, living_pawn))
- if(!istype(i))
- continue
- if(HAS_TRAIT(i, TRAIT_NEEDS_TWO_HANDS) || blackboard[BB_MONKEY_BLACKLISTITEMS][i] || i.force < blackboard[BB_MONKEY_BEST_FORCE_FOUND])
- continue
- W = i
- break
+ if(blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] && (locate(/obj/item/gun) in living_pawn.held_items))
+ // We have a gun, what could we possibly want?
+ return FALSE
- if(W)
- blackboard[BB_MONKEY_PICKUPTARGET] = W
- current_movement_target = W
- current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/ground)
- return TRUE
+ var/obj/item/weapon
+ var/list/nearby_items = list()
+ for(var/obj/item/item in oview(2, living_pawn))
+ nearby_items += item
+
+ weapon = GetBestWeapon(nearby_items, living_pawn.held_items)
+
+ var/pickpocket = FALSE
+ for(var/mob/living/carbon/human/human in oview(5, living_pawn))
+ var/obj/item/held_weapon = GetBestWeapon(human.held_items + weapon, living_pawn.held_items)
+ if(held_weapon == weapon) // It's just the same one, not a held one
+ continue
+ pickpocket = TRUE
+ weapon = held_weapon
+
+ if(!weapon || (weapon in living_pawn.held_items))
+ return FALSE
+
+ blackboard[BB_MONKEY_PICKUPTARGET] = weapon
+ current_movement_target = weapon
+ if(pickpocket)
+ current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/pickpocket)
else
- var/mob/living/carbon/human/H = locate(/mob/living/carbon/human/) in oview(2,living_pawn)
- if(H)
- W = pick(H.held_items)
- if(W && !blackboard[BB_MONKEY_BLACKLISTITEMS][W] && W.force > blackboard[BB_MONKEY_BEST_FORCE_FOUND] && !HAS_TRAIT(W, TRAIT_NEEDS_TWO_HANDS))
- blackboard[BB_MONKEY_PICKUPTARGET] = W
- current_movement_target = W
- current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/pickpocket)
- return TRUE
+ current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/ground)
+ return TRUE
+
+/// Returns either the best weapon from the given choices or null if held weapons are better
+/datum/ai_controller/monkey/proc/GetBestWeapon(list/choices, list/held_weapons)
+ var/gun_neurons_activated = blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED]
+ var/top_force = 0
+ var/obj/item/top_force_item
+ for(var/obj/item/item as anything in held_weapons)
+ if(!item)
+ continue
+ if(HAS_TRAIT(item, TRAIT_NEEDS_TWO_HANDS) || blackboard[BB_MONKEY_BLACKLISTITEMS][item])
+ continue
+ if(gun_neurons_activated && istype(item, /obj/item/gun))
+ // We have a gun, why bother looking for something inferior
+ // Also yes it is intentional that monkeys dont know how to pick the best gun
+ return item
+ if(item.force > top_force)
+ top_force = item.force
+ top_force_item = item
+
+ for(var/obj/item/item as anything in choices)
+ if(!item)
+ continue
+ if(HAS_TRAIT(item, TRAIT_NEEDS_TWO_HANDS) || blackboard[BB_MONKEY_BLACKLISTITEMS][item])
+ continue
+ if(gun_neurons_activated && istype(item, /obj/item/gun))
+ return item
+ if(item.force <= top_force)
+ continue
+ top_force_item = item
+ top_force = item.force
+
+ return top_force_item
+
+/datum/ai_controller/monkey/proc/TryFindFood()
+ . = FALSE
+ var/mob/living/living_pawn = pawn
+
+ // Held items
+
+ var/list/food_candidates = list()
+ for(var/obj/item as anything in living_pawn.held_items)
+ if(!item || !IsEdible(item))
+ continue
+ food_candidates += item
+
+ for(var/obj/item/candidate in oview(2, living_pawn))
+ if(!IsEdible(candidate))
+ continue
+ food_candidates += candidate
+
+ if(length(food_candidates))
+ var/obj/item/best_held = GetBestWeapon(null, living_pawn.held_items)
+ for(var/obj/item/held as anything in living_pawn.held_items)
+ if(!held || held == best_held)
+ continue
+ living_pawn.dropItemToGround(held)
+
+ AddBehavior(/datum/ai_behavior/consume, pick(food_candidates))
+ return TRUE
+
+/datum/ai_controller/monkey/proc/IsEdible(obj/item/thing)
+ if(IS_EDIBLE(thing))
+ return TRUE
+ if(istype(thing, /obj/item/reagent_containers/food/drinks/drinkingglass))
+ var/obj/item/reagent_containers/food/drinks/drinkingglass/glass = thing
+ if(glass.reagents.total_volume) // The glass has something in it, time to drink the mystery liquid!
+ return TRUE
+ return FALSE
//When idle just kinda fuck around.
/datum/ai_controller/monkey/PerformIdleBehavior(delta_time)
@@ -228,3 +342,7 @@ have ways of interacting with a specific mob and control it.
/datum/ai_controller/monkey/proc/target_del(target)
SIGNAL_HANDLER
blackboard[BB_MONKEY_BLACKLISTITEMS] -= target
+
+/datum/ai_controller/monkey/proc/on_eat(mob/living/pawn)
+ SIGNAL_HANDLER
+ blackboard[BB_MONKEY_NEXT_HUNGRY] = world.time + rand(120, 600) SECONDS
diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
index fac0c653b8f..0f3e2546348 100644
--- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm
+++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
@@ -17,7 +17,7 @@
TRAIT_VENTCRAWLER_NUDE,
TRAIT_PRIMITIVE,
TRAIT_WEAK_SOUL,
- TRAIT_CAN_STRIP,
+ TRAIT_GUN_NATURAL,
)
no_equip = list(ITEM_SLOT_EARS, ITEM_SLOT_EYES, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE)
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | ERT_SPAWN | SLIME_EXTRACT
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 713a4f5ba25..8458014b015 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1146,7 +1146,7 @@
if(G.trigger_guard == TRIGGER_GUARD_NONE)
to_chat(src, "You are unable to fire this!")
return FALSE
- if(G.trigger_guard != TRIGGER_GUARD_ALLOW_ALL && !ISADVANCEDTOOLUSER(src))
+ if(G.trigger_guard != TRIGGER_GUARD_ALLOW_ALL && (!ISADVANCEDTOOLUSER(src) && !HAS_TRAIT(src, TRAIT_GUN_NATURAL)))
to_chat(src, "You try to fire [G], but can't use the trigger!")
return FALSE
return TRUE