mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 08:08:49 +01:00
Adds new wrinkles to monkey brains (#58631)
This does a variety of improvements to monkey ai that I got drawn into after fixing a relatively simple bug with monkeys and guns. This pr is in support of #58565 so that in the rare chance pun pun gets a gun, they know how to use it. Previously #16630 made it so monkeys could use guns but semi-recently that was broken. Now that's fixed and in addition some other monkey ai capabilities were enhanced, read the changelog for the full list.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
"<span class='info'>[pawn] starts trying to give [held_item] to [living_target]!</span>",
|
||||
"<span class='warning'>[pawn] tries to give you [held_item]!</span>"
|
||||
)
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1146,7 +1146,7 @@
|
||||
if(G.trigger_guard == TRIGGER_GUARD_NONE)
|
||||
to_chat(src, "<span class='warning'>You are unable to fire this!</span>")
|
||||
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, "<span class='warning'>You try to fire [G], but can't use the trigger!</span>")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
Reference in New Issue
Block a user