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:
Emmett Gaines
2021-05-04 08:32:31 +01:00
committed by GitHub
parent 756e507453
commit 4367cb09bd
9 changed files with 328 additions and 58 deletions
+4 -1
View File
@@ -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
+2
View File
@@ -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 -2
View File
@@ -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
+33 -5
View File
@@ -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
+95
View File
@@ -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)
+27 -8
View File
@@ -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
+158 -40
View File
@@ -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
+1 -1
View File
@@ -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