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