mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-27 05:57:24 +01:00
Monke Business - Ports monkey AI from TG (#31067)
* Monke Mode * Punch * Some comment stuff * Linters, excess * Linters * Emote stuff * Pause monkey AI during do afters * Small improvements * Oops * Fixes monkeys trying to drink forever from a glass * Knockdowns and stamcrit fixes * Removes eating/drinking from Pun Pun * Monkey controller improvement, bug fix * Fixes monkey item giving * Fixes brain swaps * Fixes * Update code/datums/ai/monkey/monkey_controller.dm Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> * Addresses code review * Oops * Oops round 2 * Fixes monkeys staying in trip mode when evolved --------- Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
co-authored by
Burzah
parent
82d31ab8a5
commit
a590870d00
@@ -42,3 +42,32 @@
|
||||
if(can_see(controller.pawn, possible_turf, search_range))
|
||||
return possible_turf
|
||||
return null
|
||||
|
||||
/**
|
||||
* Variant of find and set that fails if the living pawn doesn't hold something
|
||||
*/
|
||||
/datum/ai_behavior/find_and_set/pawn_must_hold_item
|
||||
|
||||
/datum/ai_behavior/find_and_set/pawn_must_hold_item/search_tactic(datum/ai_controller/controller)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
if(!living_pawn.held_items())
|
||||
return // we want to fail the search if we don't have something held
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Variant of find and set that also requires the item to be edible. checks hands too
|
||||
*/
|
||||
/datum/ai_behavior/find_and_set/edible
|
||||
|
||||
/datum/ai_behavior/find_and_set/edible/search_tactic(datum/ai_controller/controller, locate_path, search_range)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
for(var/atom/held_candidate as anything in living_pawn.held_items())
|
||||
if(IsEdible(held_candidate))
|
||||
return held_candidate
|
||||
|
||||
for(var/atom/local_candidate as anything in oview(search_range, controller.pawn))
|
||||
if(IsEdible(local_candidate) && istype(local_candidate, locate_path))
|
||||
return local_candidate
|
||||
|
||||
return null
|
||||
|
||||
@@ -25,3 +25,176 @@
|
||||
if(speech_sound)
|
||||
playsound(living_pawn, speech_sound, 80, vary = TRUE)
|
||||
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/resist
|
||||
|
||||
/datum/ai_behavior/resist/perform(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, TRUE)
|
||||
living_pawn.run_resist()
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/battle_screech
|
||||
/// List of possible screeches the behavior has
|
||||
var/list/screeches
|
||||
|
||||
/datum/ai_behavior/battle_screech/perform(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
controller.set_blackboard_key(BB_BATTLE_CRY_COOLDOWN, world.time + MONKEY_BATTLE_CRY_COOLDOWN)
|
||||
INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(screeches))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/// 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(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
var/mob/living/pawn = controller.pawn
|
||||
var/obj/item/held = pawn.get_active_hand()
|
||||
if(!held)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
pawn.activate_hand()
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/// Use the currently held item, or unarmed, on a weakref to an object in the world
|
||||
/datum/ai_behavior/use_on_object
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
|
||||
|
||||
/datum/ai_behavior/use_on_object/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/use_on_object/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/mob/living/pawn = controller.pawn
|
||||
var/obj/item/held_item = pawn.get_item_by_slot(pawn.get_active_hand())
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
if(held_item)
|
||||
held_item.melee_attack_chain(pawn, target)
|
||||
else
|
||||
controller.ai_interact(target, INTENT_HELP)
|
||||
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/**
|
||||
* Drops items in hands, very important for future behaviors that require the pawn to grab stuff
|
||||
*/
|
||||
/datum/ai_behavior/drop_item
|
||||
|
||||
/datum/ai_behavior/drop_item/perform(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/list/my_held_items = living_pawn.held_items() - GetBestWeapon(controller, null, living_pawn.held_items())
|
||||
if(!length(my_held_items))
|
||||
return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY
|
||||
living_pawn.drop_item_to_ground(pick(my_held_items))
|
||||
return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY
|
||||
|
||||
/datum/ai_behavior/consume
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
|
||||
action_cooldown = 2 SECONDS
|
||||
|
||||
/datum/ai_behavior/consume/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
set_movement_target(controller, controller.blackboard[target_key])
|
||||
|
||||
/datum/ai_behavior/consume/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hunger_timer_key)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/obj/item/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
if(!(target in living_pawn.held_items()))
|
||||
if(!living_pawn.get_empty_held_indexes() || !living_pawn.put_in_hands(target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
target.melee_attack_chain(living_pawn, living_pawn)
|
||||
|
||||
if(QDELETED(target) || prob(10)) // Even if we don't finish it all we can randomly decide to be done
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
/datum/ai_behavior/find_nearby
|
||||
|
||||
/datum/ai_behavior/find_nearby/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/list/possible_targets = list()
|
||||
for(var/atom/thing in view(2, controller.pawn))
|
||||
if(!thing.mouse_opacity)
|
||||
continue
|
||||
if(thing.IsObscured())
|
||||
continue
|
||||
if(isitem(thing))
|
||||
var/obj/item/item = thing
|
||||
if(item.flags & ABSTRACT)
|
||||
continue
|
||||
possible_targets += thing
|
||||
if(!length(possible_targets))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
controller.set_blackboard_key(target_key, pick(possible_targets))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/give
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
|
||||
|
||||
/datum/ai_behavior/give/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
set_movement_target(controller, controller.blackboard[target_key])
|
||||
|
||||
/datum/ai_behavior/give/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/mob/living/pawn = controller.pawn
|
||||
var/obj/item/held_item = pawn.get_active_hand()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
|
||||
if(!held_item) // if held_item is null, we pretend that action was successful
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
if(!target || !isliving(target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
var/mob/living/living_target = target
|
||||
var/perform_flags = try_to_give_item(controller, living_target, held_item)
|
||||
if(perform_flags & AI_BEHAVIOR_FAILED)
|
||||
return perform_flags
|
||||
controller.pause_ai(1.5 SECONDS)
|
||||
living_target.visible_message(
|
||||
"<span class='notice'>[pawn] starts trying to give [held_item] to [living_target]!</span>",
|
||||
"<span class='warning'>[pawn] tries to give you [held_item]!</span>"
|
||||
)
|
||||
if(!do_after(pawn, 1 SECONDS, living_target))
|
||||
return AI_BEHAVIOR_DELAY | perform_flags
|
||||
|
||||
perform_flags |= try_to_give_item(controller, living_target, held_item, actually_give = TRUE)
|
||||
return AI_BEHAVIOR_DELAY | perform_flags
|
||||
|
||||
/datum/ai_behavior/give/proc/try_to_give_item(datum/ai_controller/controller, mob/living/carbon/target, obj/item/held_item, actually_give)
|
||||
if(QDELETED(held_item) || QDELETED(target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
if(!iscarbon(controller.pawn))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
if(!iscarbon(target))
|
||||
controller.clear_blackboard_key(BB_MONKEY_CURRENT_GIVE_TARGET)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
if(HAS_TRAIT(target, TRAIT_HANDS_BLOCKED) || target.r_hand && target.l_hand)
|
||||
controller.clear_blackboard_key(BB_MONKEY_CURRENT_GIVE_TARGET)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
var/mob/living/carbon/C = controller.pawn
|
||||
C.drop_item_to_ground(held_item)
|
||||
target.put_in_hands(held_item)
|
||||
held_item.add_fingerprint(target)
|
||||
held_item.on_give(C, target)
|
||||
held_item.pickup(target)
|
||||
controller.clear_blackboard_key(BB_MONKEY_CURRENT_GIVE_TARGET)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Generic Resist Subtree, resist if it makes sense to!
|
||||
*
|
||||
* Requires nothing beyond a living pawn, makes sense on a good amount of mobs since anything can get buckled.
|
||||
*
|
||||
* relevant blackboards:
|
||||
* * None!
|
||||
*/
|
||||
/datum/ai_planning_subtree/generic_resist/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
if(SHOULD_RESIST(living_pawn) && SPT_PROB(RESIST_SUBTREE_PROB, seconds_per_tick))
|
||||
controller.queue_behavior(/datum/ai_behavior/resist) // BRO IM ON FUCKING FIRE BRO
|
||||
return SUBTREE_RETURN_FINISH_PLANNING // IM NOT DOING ANYTHING ELSE BUT EXTINGUISH MYSELF, GOOD GOD HAVE MERCY.
|
||||
|
||||
/**
|
||||
* Generic Hunger Subtree,
|
||||
*
|
||||
* Requires at least a living mob that can hold items.
|
||||
*
|
||||
* relevant blackboards:
|
||||
* * BB_NEXT_HUNGRY - set by this subtree, is when the controller is next hungry
|
||||
*/
|
||||
/datum/ai_planning_subtree/generic_hunger/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
if(living_pawn.incapacitated()) // We're restrained or stunned - what are we gonna do?
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
var/next_eat = controller.blackboard[BB_NEXT_HUNGRY]
|
||||
if(!next_eat)
|
||||
// inits the blackboard timer
|
||||
next_eat = world.time + rand(0, 30 SECONDS)
|
||||
controller.set_blackboard_key(BB_NEXT_HUNGRY, next_eat)
|
||||
|
||||
if(world.time < next_eat)
|
||||
return
|
||||
|
||||
var/atom/food_target = controller.blackboard[BB_FOOD_TARGET]
|
||||
|
||||
if(isnull(food_target))
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/edible, BB_FOOD_TARGET, /obj/item, 2)
|
||||
return
|
||||
|
||||
if(!length(living_pawn.get_empty_held_indexes()) && !(food_target in living_pawn.held_items()))
|
||||
controller.queue_behavior(/datum/ai_behavior/drop_item)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
if(istype(food_target, /obj/item/reagent_containers))
|
||||
var/obj/item/reagent_containers/container = food_target
|
||||
if(!container.reagents.total_volume) // This thing empty.
|
||||
controller.queue_behavior(/datum/ai_behavior/drop_item)
|
||||
controller.clear_blackboard_key(BB_FOOD_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/consume, BB_FOOD_TARGET, BB_NEXT_HUNGRY)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
@@ -0,0 +1,37 @@
|
||||
/datum/idle_behavior/idle_monkey
|
||||
/// Emotes that will be played commonly during idle behavior.
|
||||
var/list/common_emotes = list(
|
||||
"screech",
|
||||
"roar",
|
||||
)
|
||||
/// Emotes that will be played rarely during idle behavior.
|
||||
var/list/rare_emotes = list(
|
||||
"scratch",
|
||||
"jump",
|
||||
"roll",
|
||||
"tail",
|
||||
)
|
||||
|
||||
/datum/idle_behavior/idle_monkey/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
if(SPT_PROB(25, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby)
|
||||
var/move_dir = pick(GLOB.alldirs)
|
||||
living_pawn.Move(get_step(living_pawn, move_dir), move_dir)
|
||||
else if(SPT_PROB(5, seconds_per_tick))
|
||||
INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(common_emotes))
|
||||
else if(SPT_PROB(1, seconds_per_tick))
|
||||
INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(rare_emotes))
|
||||
|
||||
/datum/idle_behavior/idle_monkey/pun_pun
|
||||
common_emotes = list(
|
||||
"tunesing",
|
||||
"dance",
|
||||
"bow",
|
||||
)
|
||||
rare_emotes = list(
|
||||
"clear",
|
||||
"sign",
|
||||
"tail",
|
||||
)
|
||||
@@ -0,0 +1,304 @@
|
||||
/datum/ai_behavior/battle_screech/monkey
|
||||
screeches = list("scream")
|
||||
|
||||
/datum/ai_behavior/monkey_equip
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
|
||||
|
||||
/datum/ai_behavior/monkey_equip/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/obj/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success, target_key)
|
||||
. = ..()
|
||||
|
||||
if(!success) // Don't try again on this item if we failed
|
||||
controller.set_blackboard_key_assoc(BB_MONKEY_BLACKLISTITEMS, controller.blackboard[target_key], TRUE)
|
||||
|
||||
controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET)
|
||||
|
||||
/// Equips an item on the monkey
|
||||
/// Returns TRUE if it works out, FALSE otherwise
|
||||
/datum/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
|
||||
var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND]
|
||||
if(!isturf(living_pawn.loc))
|
||||
return FALSE
|
||||
|
||||
if(!target)
|
||||
return FALSE
|
||||
|
||||
if(target.anchored) // Can't pick it up, so stop trying.
|
||||
return FALSE
|
||||
|
||||
// Strong weapon
|
||||
else if(target.force > best_force)
|
||||
living_pawn.drop_l_hand()
|
||||
living_pawn.drop_r_hand()
|
||||
living_pawn.put_in_hands(target)
|
||||
controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, target.force)
|
||||
return TRUE
|
||||
|
||||
else if(target.slot_flags) // Clothing == top priority
|
||||
living_pawn.drop_item_to_ground(target, TRUE)
|
||||
living_pawn.update_icons()
|
||||
if(!living_pawn.equip_to_appropriate_slot(target))
|
||||
return FALSE // Already wearing something, in the future this should probably replace the current item but the code didn't actually do that, and I dont want to support it right now.
|
||||
return TRUE
|
||||
|
||||
// EVERYTHING ELSE
|
||||
else if(living_pawn.get_empty_held_indexes())
|
||||
living_pawn.put_in_hands(target)
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/datum/ai_behavior/monkey_equip/ground
|
||||
|
||||
/datum/ai_behavior/monkey_equip/ground/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
if(equip_item(controller))
|
||||
return . | AI_BEHAVIOR_SUCCEEDED
|
||||
return . | AI_BEHAVIOR_FAILED
|
||||
|
||||
/datum/ai_behavior/monkey_equip/pickpocket
|
||||
|
||||
/datum/ai_behavior/monkey_equip/pickpocket/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) // We are pickpocketing, don't do ANYTHING!!!!
|
||||
return
|
||||
INVOKE_ASYNC(src, PROC_REF(attempt_pickpocket), controller)
|
||||
|
||||
/datum/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller)
|
||||
var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
|
||||
var/mob/living/victim = target.loc
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
if(!istype(victim))
|
||||
finish_action(controller, FALSE)
|
||||
return
|
||||
|
||||
victim.visible_message(SPAN_WARNING("[living_pawn] starts trying to take [target] from [victim]!"), SPAN_DANGER("[living_pawn] tries to take [target]!"))
|
||||
|
||||
controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, TRUE)
|
||||
|
||||
var/success = FALSE
|
||||
|
||||
if(do_after(living_pawn, MONKEY_ITEM_SNATCH_DELAY, victim) && target && living_pawn.Adjacent(victim))
|
||||
for(var/obj/item/I in victim.held_items())
|
||||
if(I == target)
|
||||
victim.visible_message(SPAN_DANGER("[living_pawn] snatches [target] from [victim]."), SPAN_USERDANGER("[living_pawn] snatched [target]!"))
|
||||
if(victim.unequip(target))
|
||||
if(!QDELETED(target) && !equip_item(controller))
|
||||
target.forceMove(living_pawn.drop_location())
|
||||
success = TRUE
|
||||
break
|
||||
else
|
||||
victim.visible_message(SPAN_DANGER("[living_pawn] tried to snatch [target] from [victim], but failed!"), SPAN_USERDANGER("[living_pawn] tried to grab [target]!?"))
|
||||
|
||||
finish_action(controller, success) // We either fucked up or got the item.
|
||||
|
||||
/datum/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success)
|
||||
. = ..()
|
||||
controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, FALSE)
|
||||
controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET)
|
||||
|
||||
/datum/ai_behavior/monkey_flee
|
||||
|
||||
/datum/ai_behavior/monkey_flee/perform(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
if(living_pawn.health >= MONKEY_FLEE_HEALTH) // we're back in business
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
var/mob/living/target = null
|
||||
|
||||
// flee from anyone who attacked us and we didn't beat down
|
||||
for(var/mob/living/L in view(living_pawn, MONKEY_FLEE_VISION))
|
||||
if(controller.blackboard[BB_MONKEY_ENEMIES][L] && L.stat == CONSCIOUS)
|
||||
target = L
|
||||
break
|
||||
|
||||
if(target)
|
||||
GLOB.move_manager.move_away(living_pawn, target, max_dist=MONKEY_ENEMY_VISION, delay=5)
|
||||
return AI_BEHAVIOR_DELAY
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/monkey_attack_mob
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION // performs to increase frustration
|
||||
|
||||
/datum/ai_behavior/monkey_attack_mob/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
set_movement_target(controller, controller.blackboard[target_key])
|
||||
|
||||
/datum/ai_behavior/monkey_attack_mob/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[BB_TARGETING_STRATEGY])
|
||||
|
||||
if(QDELETED(target) || !strategy.can_attack(living_pawn, target)) // Target == owned
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
// check if target has a weapon
|
||||
var/holding_weapon
|
||||
for(var/obj/item/potential_weapon in target.held_items())
|
||||
if(!(potential_weapon.flags & ABSTRACT))
|
||||
holding_weapon = potential_weapon
|
||||
break
|
||||
|
||||
var/attack_results = monkey_attack(controller, target, seconds_per_tick, holding_weapon && SPT_PROB(MONKEY_ATTACK_DISARM_PROB, seconds_per_tick), holding_weapon)
|
||||
|
||||
if(!attack_results || controller.blackboard[BB_MONKEY_AGGRESSIVE])
|
||||
return AI_BEHAVIOR_DELAY
|
||||
|
||||
// check if we can de-aggro on the enemy...
|
||||
var/hatred_value = controller.blackboard[BB_MONKEY_ENEMIES][target]
|
||||
|
||||
if(isnull(hatred_value))
|
||||
hatred_value = 1
|
||||
controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value)
|
||||
|
||||
if(!SPT_PROB(MONKEY_HATRED_REDUCTION_PROB, seconds_per_tick))
|
||||
return AI_BEHAVIOR_DELAY
|
||||
|
||||
// we decrease our hatred value to them by 1
|
||||
hatred_value--
|
||||
if(hatred_value <= 0)
|
||||
controller.remove_thing_from_blackboard_key(BB_MONKEY_ENEMIES, target)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value)
|
||||
return AI_BEHAVIOR_DELAY
|
||||
|
||||
/datum/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded, target_key)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
/// 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, seconds_per_tick, disarm, holding_weapon)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
if(living_pawn.next_move > world.time)
|
||||
return FALSE
|
||||
|
||||
// are we holding a gun? can we shoot it? if so, FIRE
|
||||
var/obj/item/gun/gun_to_shoot = locate() in living_pawn.held_items()
|
||||
if(gun_to_shoot?.can_shoot())
|
||||
if(gun_to_shoot != living_pawn.get_active_hand())
|
||||
living_pawn.swap_hand()
|
||||
controller.ai_interact(target, INTENT_HARM)
|
||||
return TRUE
|
||||
|
||||
// look for any potential weapons we're holding
|
||||
var/obj/item/potential_weapon = locate() in living_pawn.held_items()
|
||||
if(!living_pawn.Adjacent(target, potential_weapon))
|
||||
return FALSE
|
||||
|
||||
if(isnull(potential_weapon))
|
||||
controller.ai_interact(target, INTENT_HARM)
|
||||
if(disarm && !isnull(holding_weapon) && controller.blackboard[BB_MONKEY_BLACKLISTITEMS][holding_weapon])
|
||||
controller.remove_thing_from_blackboard_key(BB_MONKEY_BLACKLISTITEMS, holding_weapon) // lets try to pickpocket it again!
|
||||
return TRUE
|
||||
|
||||
if(potential_weapon != living_pawn.get_active_hand())
|
||||
living_pawn.swap_hand()
|
||||
controller.ai_interact(target, INTENT_HARM)
|
||||
return TRUE
|
||||
|
||||
/datum/ai_behavior/disposal_mob
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM // performs to increase frustration
|
||||
|
||||
/datum/ai_behavior/disposal_mob/setup(datum/ai_controller/controller, attack_target_key, disposal_target_key)
|
||||
. = ..()
|
||||
set_movement_target(controller, controller.blackboard[attack_target_key])
|
||||
|
||||
/datum/ai_behavior/disposal_mob/finish_action(datum/ai_controller/controller, succeeded, attack_target_key, disposal_target_key)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(attack_target_key) // Reset attack target
|
||||
controller.set_blackboard_key(BB_MONKEY_DISPOSING, FALSE) // No longer disposing
|
||||
controller.clear_blackboard_key(disposal_target_key) // No target disposal
|
||||
|
||||
/datum/ai_behavior/disposal_mob/perform(seconds_per_tick, datum/ai_controller/controller, attack_target_key, disposal_target_key)
|
||||
. = ..()
|
||||
if(controller.blackboard[BB_MONKEY_DISPOSING]) // We are disposing, don't do ANYTHING!!!!
|
||||
return AI_BEHAVIOR_DELAY
|
||||
|
||||
var/mob/living/target = controller.blackboard[attack_target_key]
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
set_movement_target(controller, target)
|
||||
|
||||
if(!target)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) // Dont steal from my fellow monkeys.
|
||||
if(living_pawn.Adjacent(target) && isturf(target.loc))
|
||||
living_pawn.start_pulling(target)
|
||||
return AI_BEHAVIOR_DELAY // Do the rest next turn
|
||||
|
||||
var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key]
|
||||
set_movement_target(controller, disposal)
|
||||
|
||||
if(!disposal)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
if(living_pawn.Adjacent(disposal))
|
||||
INVOKE_ASYNC(src, PROC_REF(try_disposal_mob), controller, attack_target_key, disposal_target_key) // put him in!
|
||||
return AI_BEHAVIOR_DELAY
|
||||
// This means we might be getting pissed!
|
||||
return AI_BEHAVIOR_DELAY
|
||||
|
||||
/datum/ai_behavior/disposal_mob/proc/try_disposal_mob(datum/ai_controller/controller, attack_target_key, disposal_target_key)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/mob/living/target = controller.blackboard[attack_target_key]
|
||||
var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key]
|
||||
|
||||
controller.set_blackboard_key(BB_MONKEY_DISPOSING, TRUE)
|
||||
|
||||
if(target && disposal?.stuff_mob_in(target, living_pawn))
|
||||
disposal.flush()
|
||||
finish_action(controller, TRUE, attack_target_key, disposal_target_key)
|
||||
|
||||
/datum/ai_behavior/recruit_monkeys/perform(seconds_per_tick, datum/ai_controller/controller)
|
||||
. = ..()
|
||||
controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
for(var/mob/living/nearby_monkey in view(living_pawn, MONKEY_ENEMY_VISION))
|
||||
if(QDELETED(nearby_monkey) || !HAS_AI_CONTROLLER_TYPE(nearby_monkey, /datum/ai_controller/monkey))
|
||||
continue
|
||||
if(!SPT_PROB(MONKEY_RECRUIT_PROB, seconds_per_tick))
|
||||
continue
|
||||
// Recruited a monkey to our side
|
||||
controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN)
|
||||
// Other monkeys now also hate the guy we're currently targeting
|
||||
nearby_monkey.ai_controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET], MONKEY_RECRUIT_HATED_AMOUNT)
|
||||
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/monkey_set_combat_target/perform(seconds_per_tick, datum/ai_controller/controller, set_key, enemies_key)
|
||||
. = ..()
|
||||
var/list/enemies = controller.blackboard[enemies_key]
|
||||
var/list/valids = list()
|
||||
for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, controller.pawn))
|
||||
if(possible_enemy == controller.pawn)
|
||||
continue // don't target ourselves
|
||||
if(!enemies[possible_enemy]) // We don't hate this creature! But we might still attack it!
|
||||
if(!controller.blackboard[BB_MONKEY_AGGRESSIVE]) // We are not aggressive either, so we won't attack!
|
||||
continue
|
||||
if(faction_check(possible_enemy.faction, list("monkey", "jungle"), exact_match = FALSE) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) // do not target your team. includes monkys gorillas etc.
|
||||
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(controller.pawn, possible_enemy) || 1), 1)
|
||||
|
||||
if(!length(valids))
|
||||
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
|
||||
|
||||
controller.set_blackboard_key(set_key, pickweight(valids))
|
||||
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
AI controllers are a datumized form of AI that simulates the input a player would otherwise give to a mob. What this means is that these datums
|
||||
have ways of interacting with a specific mob and control it.
|
||||
*/
|
||||
/// OOK OOK OOK
|
||||
|
||||
/datum/ai_controller/monkey
|
||||
ai_movement = /datum/ai_movement/jps
|
||||
movement_delay = 0.4 SECONDS
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/generic_resist,
|
||||
/datum/ai_planning_subtree/monkey_combat,
|
||||
/datum/ai_planning_subtree/generic_hunger,
|
||||
/datum/ai_planning_subtree/monkey_shenanigans,
|
||||
)
|
||||
|
||||
ai_traits = AI_FLAG_PAUSE_DURING_DO_AFTER
|
||||
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/monkey,
|
||||
BB_MONKEY_AGGRESSIVE = FALSE,
|
||||
BB_MONKEY_BEST_FORCE_FOUND = 0,
|
||||
BB_MONKEY_ENEMIES = list(),
|
||||
BB_MONKEY_BLACKLISTITEMS = list(),
|
||||
BB_MONKEY_PICKPOCKETING = FALSE,
|
||||
BB_MONKEY_DISPOSING = FALSE,
|
||||
BB_MONKEY_GUN_NEURONS_ACTIVATED = FALSE,
|
||||
BB_RESISTING = FALSE,
|
||||
)
|
||||
idle_behavior = /datum/idle_behavior/idle_monkey
|
||||
/// Is this mob tripping things?
|
||||
var/tripping = FALSE
|
||||
|
||||
/datum/targeting_strategy/basic/monkey
|
||||
|
||||
/datum/targeting_strategy/basic/monkey/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target)
|
||||
if(controller.blackboard[BB_MONKEY_ENEMIES][the_target])
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/ai_controller/monkey/process(seconds_per_tick)
|
||||
var/mob/living/living_pawn = src.pawn
|
||||
movement_delay = living_pawn.movement_delay() // Circumstances change. Update speed frequently.
|
||||
|
||||
if(!length(living_pawn.do_afters) && living_pawn.ai_controller.blackboard[BB_RESISTING])
|
||||
living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, FALSE)
|
||||
|
||||
if(living_pawn.ai_controller.blackboard[BB_RESISTING])
|
||||
return
|
||||
|
||||
if(living_pawn.IsWeakened() || living_pawn.IsStunned()) // We're stunned - what are we gonna do?
|
||||
cancel_actions()
|
||||
return
|
||||
|
||||
. = ..()
|
||||
|
||||
/datum/ai_controller/monkey/pun_pun
|
||||
movement_delay = 7 DECISECONDS // pun pun moves slower so the bartender can keep track of them
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/generic_resist,
|
||||
/datum/ai_planning_subtree/monkey_combat,
|
||||
/datum/ai_planning_subtree/punpun_shenanigans,
|
||||
)
|
||||
idle_behavior = /datum/idle_behavior/idle_monkey/pun_pun
|
||||
|
||||
/datum/ai_controller/monkey/angry
|
||||
|
||||
/datum/ai_controller/monkey/angry/try_possess_pawn(atom/new_pawn)
|
||||
. = ..()
|
||||
if(. & AI_CONTROLLER_INCOMPATIBLE)
|
||||
return
|
||||
pawn = new_pawn
|
||||
set_blackboard_key(BB_MONKEY_AGGRESSIVE, TRUE) // Angry monke
|
||||
set_trip_mode(mode = FALSE)
|
||||
|
||||
/datum/ai_controller/monkey/try_possess_pawn(atom/new_pawn)
|
||||
if(!isliving(new_pawn))
|
||||
return AI_CONTROLLER_INCOMPATIBLE
|
||||
|
||||
var/mob/living/living_pawn = new_pawn
|
||||
if(!HAS_TRAIT(living_pawn, TRAIT_RELAYING_ATTACKER))
|
||||
living_pawn.AddElement(/datum/element/relay_attackers)
|
||||
RegisterSignal(new_pawn, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked))
|
||||
RegisterSignal(new_pawn, COMSIG_ATOM_PULLED, PROC_REF(on_startpulling))
|
||||
RegisterSignals(new_pawn, list(COMSIG_LIVING_TRY_SYRINGE_INJECT, COMSIG_LIVING_TRY_SYRINGE_WITHDRAW), PROC_REF(on_try_syringe))
|
||||
RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(on_attempt_cuff))
|
||||
RegisterSignal(new_pawn, COMSIG_LIVING_MOB_BUMP, PROC_REF(on_mob_bump))
|
||||
return ..() // Run parent at end
|
||||
|
||||
/datum/ai_controller/monkey/unpossess_pawn(destroy)
|
||||
UnregisterSignal(pawn, list(
|
||||
COMSIG_ATOM_WAS_ATTACKED,
|
||||
COMSIG_ATOM_PULLED,
|
||||
COMSIG_LIVING_TRY_SYRINGE_INJECT,
|
||||
COMSIG_LIVING_TRY_SYRINGE_WITHDRAW,
|
||||
COMSIG_CARBON_CUFF_ATTEMPTED,
|
||||
COMSIG_LIVING_MOB_BUMP
|
||||
))
|
||||
|
||||
return ..() // Run parent at end
|
||||
|
||||
/datum/ai_controller/monkey/on_sentience_lost()
|
||||
. = ..()
|
||||
set_trip_mode(mode = TRUE)
|
||||
|
||||
/datum/ai_controller/monkey/get_able_to_run()
|
||||
var/mob/living/living_pawn = pawn
|
||||
|
||||
if(living_pawn.stat || living_pawn.incapacitated())
|
||||
return AI_UNABLE_TO_RUN
|
||||
|
||||
if(living_pawn.ckey || living_pawn.client)
|
||||
return AI_UNABLE_TO_RUN
|
||||
return ..()
|
||||
|
||||
/datum/ai_controller/monkey/proc/set_trip_mode(mode = TRUE)
|
||||
if(!ismonkeybasic(pawn)) // In case the monkey was humanized.
|
||||
tripping = FALSE
|
||||
return
|
||||
tripping = mode
|
||||
|
||||
/// re-used behavior pattern by monkeys for finding a weapon
|
||||
/datum/ai_controller/monkey/proc/TryFindWeapon()
|
||||
var/mob/living/living_pawn = pawn
|
||||
|
||||
if(!living_pawn.held_items())
|
||||
set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, 0)
|
||||
|
||||
if(blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] && is_type_in_list(/obj/item/gun, living_pawn.held_items()))
|
||||
// We have a gun, what could we possibly want?
|
||||
return FALSE
|
||||
|
||||
var/obj/item/weapon
|
||||
var/list/nearby_items = list()
|
||||
for(var/obj/item/item in oview(2, living_pawn))
|
||||
if(item.force < 2)
|
||||
continue
|
||||
nearby_items += item
|
||||
|
||||
for(var/obj/item/item in living_pawn.held_items()) // If we've got some garbage in our hands that's going to stop us from effectively attacking, we should get rid of it.
|
||||
if(item.force < 2)
|
||||
living_pawn.drop_item_to_ground(item)
|
||||
|
||||
weapon = GetBestWeapon(src, 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(src, 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
|
||||
|
||||
if(weapon.force < 2) // our bite does 2 damage on average, no point in settling for anything less
|
||||
return FALSE
|
||||
|
||||
set_blackboard_key(BB_MONKEY_PICKUPTARGET, weapon)
|
||||
if(pickpocket)
|
||||
queue_behavior(/datum/ai_behavior/monkey_equip/pickpocket, BB_MONKEY_PICKUPTARGET)
|
||||
else
|
||||
queue_behavior(/datum/ai_behavior/monkey_equip/ground, BB_MONKEY_PICKUPTARGET)
|
||||
return TRUE
|
||||
|
||||
/// Reactive events to being hit
|
||||
/datum/ai_controller/monkey/proc/retaliate(mob/living/living_mob)
|
||||
// just to be safe
|
||||
if(QDELETED(living_mob))
|
||||
return
|
||||
|
||||
add_blackboard_key_assoc(BB_MONKEY_ENEMIES, living_mob, MONKEY_HATRED_AMOUNT)
|
||||
|
||||
/datum/ai_controller/monkey/proc/on_attacked(datum/source, mob/attacker)
|
||||
SIGNAL_HANDLER
|
||||
if(prob(MONKEY_RETALIATE_PROB))
|
||||
retaliate(attacker)
|
||||
|
||||
/datum/ai_controller/monkey/proc/on_startpulling(datum/source, atom/movable/puller, state, force)
|
||||
SIGNAL_HANDLER
|
||||
var/mob/living/living_pawn = pawn
|
||||
if(living_pawn.stat && prob(MONKEY_PULL_AGGRO_PROB)) // nuh uh you don't pull me!
|
||||
retaliate(living_pawn.pulledby)
|
||||
return TRUE
|
||||
|
||||
/datum/ai_controller/monkey/proc/on_try_syringe(datum/source, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
// chance of monkey retaliation
|
||||
if(prob(MONKEY_SYRINGE_RETALIATION_PROB))
|
||||
retaliate(user)
|
||||
|
||||
/datum/ai_controller/monkey/proc/on_attempt_cuff(datum/source, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
// chance of monkey retaliation
|
||||
if(prob(MONKEY_CUFF_RETALIATION_PROB))
|
||||
retaliate(user)
|
||||
|
||||
/datum/ai_controller/monkey/proc/on_mob_bump(mob/source, mob/living/crossing_mob)
|
||||
SIGNAL_HANDLER
|
||||
if(!tripping)
|
||||
return
|
||||
crossing_mob.KnockDown(4 SECONDS)
|
||||
@@ -0,0 +1,79 @@
|
||||
/datum/ai_planning_subtree/monkey_shenanigans/select_behaviors(datum/ai_controller/monkey/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
if(living_pawn.incapacitated()) // We're restrained or stunned - what are we gonna do?
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
if(prob(5))
|
||||
controller.queue_behavior(/datum/ai_behavior/use_in_hand)
|
||||
|
||||
if(!SPT_PROB(MONKEY_SHENANIGAN_PROB, seconds_per_tick))
|
||||
return
|
||||
|
||||
if(!controller.blackboard[BB_MONKEY_CURRENT_PRESS_TARGET])
|
||||
controller.queue_behavior(/datum/ai_behavior/find_nearby, BB_MONKEY_CURRENT_PRESS_TARGET)
|
||||
return
|
||||
|
||||
if(prob(50))
|
||||
controller.queue_behavior(/datum/ai_behavior/use_on_object, BB_MONKEY_CURRENT_PRESS_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
if(!controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET])
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/pawn_must_hold_item, BB_MONKEY_CURRENT_GIVE_TARGET, /mob/living, 2)
|
||||
else
|
||||
if(prob(5))
|
||||
controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
controller.TryFindWeapon()
|
||||
|
||||
/// monkey combat subtree.
|
||||
/datum/ai_planning_subtree/monkey_combat/select_behaviors(datum/ai_controller/monkey/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES]
|
||||
|
||||
if(living_pawn.incapacitated()) // We're restrained or stunned - what are we gonna do?
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
if((HAS_TRAIT(controller.pawn, TRAIT_PACIFISM)) || (!length(enemies) && !controller.blackboard[BB_MONKEY_AGGRESSIVE])) // Pacifist, or we have no enemies and we're not pissed
|
||||
living_pawn.a_intent = INTENT_HELP
|
||||
return
|
||||
|
||||
if(!controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET])
|
||||
controller.queue_behavior(/datum/ai_behavior/monkey_set_combat_target, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_ENEMIES)
|
||||
living_pawn.a_intent = INTENT_HELP
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
var/mob/living/selected_enemy = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
|
||||
|
||||
if(QDELETED(selected_enemy))
|
||||
living_pawn.a_intent = INTENT_HELP
|
||||
return
|
||||
|
||||
if(!selected_enemy.stat) // He's up, get him!
|
||||
living_pawn.a_intent = INTENT_HARM
|
||||
if(living_pawn.health < MONKEY_FLEE_HEALTH) // Time to skeddadle
|
||||
controller.queue_behavior(/datum/ai_behavior/monkey_flee)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING // I'm running fuck you guys
|
||||
|
||||
if(controller.TryFindWeapon()) // Getting a weapon is higher priority if im not fleeing.
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
if(controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time)
|
||||
controller.queue_behavior(/datum/ai_behavior/recruit_monkeys, BB_MONKEY_CURRENT_ATTACK_TARGET)
|
||||
return
|
||||
|
||||
if(controller.blackboard[BB_BATTLE_CRY_COOLDOWN] < world.time)
|
||||
controller.queue_behavior(/datum/ai_behavior/battle_screech/monkey)
|
||||
controller.queue_behavior(/datum/ai_behavior/monkey_attack_mob, BB_MONKEY_CURRENT_ATTACK_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
// by this point we have a target but they're down, let's try dumpstering this loser
|
||||
|
||||
living_pawn.a_intent = INTENT_HELP
|
||||
|
||||
if(!controller.blackboard[BB_MONKEY_TARGET_DISPOSAL])
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_MONKEY_TARGET_DISPOSAL, /obj/machinery/disposal, MONKEY_ENEMY_VISION)
|
||||
return
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/disposal_mob, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_TARGET_DISPOSAL)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
@@ -0,0 +1,22 @@
|
||||
/datum/ai_planning_subtree/punpun_shenanigans/select_behaviors(datum/ai_controller/monkey/controller, seconds_per_tick)
|
||||
|
||||
controller.set_trip_mode(mode = FALSE) // pun pun doesn't fuck around
|
||||
|
||||
if(prob(5))
|
||||
controller.queue_behavior(/datum/ai_behavior/use_in_hand)
|
||||
|
||||
if(!SPT_PROB(MONKEY_SHENANIGAN_PROB, seconds_per_tick))
|
||||
return
|
||||
|
||||
if(!controller.blackboard[BB_MONKEY_CURRENT_PRESS_TARGET])
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_MONKEY_CURRENT_PRESS_TARGET, /obj/item/desk_bell, 2)
|
||||
else if(prob(50))
|
||||
controller.queue_behavior(/datum/ai_behavior/use_on_object, BB_MONKEY_CURRENT_PRESS_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
if(!controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET])
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/pawn_must_hold_item, BB_MONKEY_CURRENT_GIVE_TARGET, /mob/living, 2)
|
||||
else if(prob(30))
|
||||
controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
linked_emote = /datum/emote/living/choke
|
||||
name = "Choke"
|
||||
|
||||
/datum/keybinding/emote/sing_tune
|
||||
linked_emote = /datum/emote/living/sing_tune
|
||||
name = "Sing Tune"
|
||||
|
||||
/datum/keybinding/emote/collapse
|
||||
linked_emote = /datum/emote/living/collapse
|
||||
name = "Collapse"
|
||||
@@ -349,6 +353,10 @@
|
||||
linked_emote = /datum/emote/living/carbon/human/eyebrow
|
||||
name = "Eyebrow"
|
||||
|
||||
/datum/keybinding/emote/carbon/human/clear_throat
|
||||
linked_emote = /datum/emote/living/carbon/human/clear_throat
|
||||
name = "Clear Throat"
|
||||
|
||||
/datum/keybinding/emote/carbon/human/facepalm
|
||||
linked_emote = /datum/emote/living/carbon/human/facepalm
|
||||
name = "Facepalm"
|
||||
|
||||
Reference in New Issue
Block a user