[MIRROR] Monkey subtree breakup refactor! [MDB IGNORE] (#8748)

* Monkey subtree breakup refactor! (#61741)

Splitting up monkey ai into subtrees allows me to make a punpun ai after this pr is merged, makes stopping planning matter for the AI subtrees, and more generic subtrees that can be used by most ais

It also gets rid of bad practices like setting blackboards in the ai controller.

* Monkey subtree breakup refactor!

Co-authored-by: tralezab <40974010+tralezab@users.noreply.github.com>
This commit is contained in:
SkyratBot
2021-10-11 16:16:48 +01:00
committed by GitHub
co-authored by tralezab
parent d82e654475
commit 87f9a90cea
13 changed files with 363 additions and 198 deletions
+16 -10
View File
@@ -4,11 +4,6 @@
#define AI_STATUS_ON 1
#define AI_STATUS_OFF 2
///Monkey checks
#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || (source.pulledby && source.pulledby.grab_state > GRAB_PASSIVE))
#define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat)
///For JPS pathing, the maximum length of a path we'll try to generate. Should be modularized depending on what we're doing later on
#define AI_MAX_PATH_LENGTH 30 // 30 is possibly overkill since by default we lose interest after 14 tiles of distance, but this gives wiggle room for weaving around obstacles
@@ -27,22 +22,34 @@
///AI flags
#define STOP_MOVING_WHEN_PULLED (1<<0)
///Subtree defines
//Base Subtree defines
///This subtree should cancel any further planning, (Including from other subtrees)
#define SUBTREE_RETURN_FINISH_PLANNING 1
//Generic subtree defines
/// probability that the pawn should try resisting out of restraints
#define RESIST_SUBTREE_PROB 50
///macro for whether it's appropriate to resist right now, used by resist subtree
#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || (source.pulledby && source.pulledby.grab_state > GRAB_PASSIVE))
///macro for whether the pawn can act, used generally to prevent some horrifying ai disasters
#define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat)
//Generic BB keys
#define BB_CURRENT_MIN_MOVE_DISTANCE "min_move_distance"
///time until we should next eat, set by the generic hunger subtree
#define BB_NEXT_HUNGRY "BB_NEXT_HUNGRY"
///what we're going to eat next
#define BB_FOOD_TARGET "bb_food_target"
//for songs
///song datum blackboard, set by instrument subtrees
#define BB_SONG_DATUM "BB_SONG_DATUM"
///song instrument blackboard, set by instrument subtrees
#define BB_SONG_INSTRUMENT "BB_SONG_INSTRUMENT"
///song lines blackboard, set by default on controllers
#define BB_SONG_LINES "song_lines"
// Monkey AI controller blackboard keys
#define BB_MONKEY_AGGRESSIVE "BB_monkey_aggressive"
@@ -59,7 +66,6 @@
#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
@@ -14,8 +14,6 @@
#define MONKEY_SYRINGE_RETALIATION_PROB 20
// Probability per Life tick that the monkey will:
/// probability that monkey resist out of restraints
#define MONKEY_RESIST_PROB 50
/// probability that monkey aggro against the mob pulling it
#define MONKEY_PULL_AGGRO_PROB 5
/// probability that monkey will get into mischief, i.e. finding/stealing items
+46
View File
@@ -0,0 +1,46 @@
//this file has procs that help ais think good. used by behaviors mostly
/// Returns either the best weapon from the given choices or null if held weapons are better
/proc/GetBestWeapon(datum/ai_controller/controller, list/choices, list/held_weapons)
var/gun_neurons_activated = controller.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) || controller.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 pawns 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) || controller.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
///returns if something can be consumed, drink or food
/proc/IsEdible(obj/item/thing)
if(!istype(thing))
return FALSE
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
@@ -1,9 +1,4 @@
/datum/ai_behavior/find_and_set/cursed
//optional, don't use if you're changing search_tactic()
locate_path = /mob/living/carbon
bb_key_to_set = BB_CURSE_TARGET
/datum/ai_behavior/item_move_close_and_attack/cursed
attack_sound = 'sound/items/haunted/ghostitemattack.ogg'
max_attempts = 4
+1 -1
View File
@@ -4,7 +4,7 @@
//make sure we have a target
var/mob/living/carbon/curse_target = controller.blackboard[BB_CURSE_TARGET]
if(!curse_target)
controller.queue_behavior(/datum/ai_behavior/find_and_set/cursed)
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_CURSE_TARGET, /mob/living/carbon, CURSED_VIEW_RANGE)
return
//make sure attack is valid
if(get_dist(curse_target, item_pawn) > CURSED_VIEW_RANGE)
+97 -24
View File
@@ -115,7 +115,6 @@
. = ..()
controller.current_movement_target = controller.blackboard[target_key]
/datum/ai_behavior/give/perform(delta_time, datum/ai_controller/controller, target_key)
. = ..()
var/mob/living/pawn = controller.pawn
@@ -150,25 +149,31 @@
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
action_cooldown = 2 SECONDS
/datum/ai_behavior/consume/setup(datum/ai_controller/controller, obj/item/target)
/datum/ai_behavior/consume/setup(datum/ai_controller/controller, target_key)
. = ..()
controller.current_movement_target = target
controller.current_movement_target = controller.blackboard[target_key]
/datum/ai_behavior/consume/perform(delta_time, datum/ai_controller/controller, obj/item/target)
/datum/ai_behavior/consume/perform(delta_time, datum/ai_controller/controller, target_key, hunger_timer_key)
. = ..()
var/mob/living/pawn = controller.pawn
var/mob/living/living_pawn = controller.pawn
var/obj/item/target = controller.blackboard[target_key]
if(!(target in pawn.held_items))
if(!pawn.put_in_hand_check(target))
finish_action(controller, FALSE)
if(!(target in living_pawn.held_items))
if(!living_pawn.put_in_hand_check(target))
finish_action(controller, FALSE, target, hunger_timer_key)
return
pawn.put_in_hands(target)
living_pawn.put_in_hands(target)
target.melee_attack_chain(pawn, pawn)
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
finish_action(controller, TRUE)
finish_action(controller, TRUE, null, hunger_timer_key)
/datum/ai_behavior/consume/finish_action(datum/ai_controller/controller, succeeded, target_key, hunger_timer_key)
. = ..()
if(succeeded)
controller.blackboard[hunger_timer_key] = world.time + rand(12 SECONDS, 60 SECONDS)
/**find and set
* Finds an item near themselves, sets a blackboard key as it. Very useful for ais that need to use machines or something.
@@ -177,24 +182,73 @@
*/
/datum/ai_behavior/find_and_set
action_cooldown = 5 SECONDS
///search range in how many tiles around the pawn to look for the path
var/search_range = 7
//optional, don't use if you're changing search_tactic()
var/locate_path
var/bb_key_to_set
/datum/ai_behavior/find_and_set/perform(delta_time, datum/ai_controller/controller)
/datum/ai_behavior/find_and_set/perform(delta_time, datum/ai_controller/controller, set_key, locate_path, search_range)
. = ..()
var/find_this_thing = search_tactic(controller)
var/find_this_thing = search_tactic(controller, locate_path, search_range)
if(find_this_thing)
controller.blackboard[bb_key_to_set] = find_this_thing
controller.blackboard[set_key] = find_this_thing
finish_action(controller, TRUE)
else
finish_action(controller, FALSE)
/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller)
/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range)
return locate(locate_path) in oview(search_range, controller.pawn)
/**
* 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.get_num_held_items())
return //we want to fail the search if we don't have something held
. = ..()
/**
* 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
var/list/food_candidates = list()
for(var/held_candidate as anything in living_pawn.held_items)
if(!held_candidate || !IsEdible(held_candidate))
continue
food_candidates += held_candidate
var/list/local_results = locate(locate_path) in oview(search_range, controller.pawn)
for(var/local_candidate in local_results)
if(!IsEdible(local_candidate))
continue
food_candidates += local_candidate
if(food_candidates.len)
return pick(food_candidates)
/**
* Variant of find and set that only checks in hands, search range should be excluded for this
*/
/datum/ai_behavior/find_and_set/in_hands
/datum/ai_behavior/find_and_set/in_hands/search_tactic(datum/ai_controller/controller, locate_path)
var/mob/living/living_pawn = controller.pawn
return locate(locate_path) in living_pawn.held_items
/**
* 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(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/living_pawn = controller.pawn
var/obj/item/best_held = GetBestWeapon(controller, 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)
/// This behavior involves attacking a target.
/datum/ai_behavior/attack
@@ -284,10 +338,11 @@
/datum/ai_behavior/setup_instrument
/datum/ai_behavior/setup_instrument/perform(delta_time, datum/ai_controller/controller, song_datum_key, song_lines_key)
/datum/ai_behavior/setup_instrument/perform(delta_time, datum/ai_controller/controller, song_instrument_key, song_lines_key)
. = ..()
var/datum/song/song = controller.blackboard[song_datum_key]
var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key]
var/datum/song/song = song_instrument.song
var/song_lines = controller.blackboard[song_lines_key]
//just in case- it won't do anything if the instrument isn't playing
@@ -299,10 +354,28 @@
/datum/ai_behavior/play_instrument
/datum/ai_behavior/play_instrument/perform(delta_time, datum/ai_controller/controller, song_datum_key)
/datum/ai_behavior/play_instrument/perform(delta_time, datum/ai_controller/controller, song_instrument_key)
. = ..()
var/datum/song/song = controller.blackboard[song_datum_key]
var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key]
var/datum/song/song = song_instrument.song
song.start_playing(controller.pawn)
finish_action(controller, TRUE)
/datum/ai_behavior/find_nearby
/datum/ai_behavior/find_nearby/perform(delta_time, 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
possible_targets += thing
if(!possible_targets.len)
finish_action(controller, FALSE)
controller.blackboard[target_key] = pick(possible_targets)
finish_action(controller, TRUE)
+57 -11
View File
@@ -1,16 +1,62 @@
/datum/ai_planning_subtree/play_instrument/SelectBehaviors(datum/ai_controller/monkey/controller, delta_time)
var/mob/living/living_pawn = controller.pawn
var/obj/item/instrument/song_player = locate(/obj/item/instrument) in living_pawn.held_items
if(!song_player)
controller.blackboard[BB_SONG_DATUM] = null
/**
* Generic Instrument Subtree, For your pawn playing instruments
*
* Requires at least a living mob that can hold items.
*
* relevant blackboards:
* * BB_SONG_INSTRUMENT - set by this subtree, is the song datum the pawn plays music from.
* * BB_SONG_LINES - not set by this subtree, is the song loaded into the song datum.
*/
/datum/ai_planning_subtree/generic_play_instrument/SelectBehaviors(datum/ai_controller/controller, delta_time)
if(!controller.blackboard[BB_SONG_INSTRUMENT])
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands, BB_SONG_INSTRUMENT, /obj/item/instrument)
return //we can't play a song since we do not have an instrument
controller.blackboard[BB_SONG_DATUM] = song_player.song
var/obj/item/instrument/song_player = controller.blackboard[BB_SONG_INSTRUMENT]
var/list/donkey_kong_lines = splittext(MONKEY_SONG, "\n")
popleft(donkey_kong_lines) //remove BPM as it is parsed out
if(!compare_list(song_player.song.lines, donkey_kong_lines) || !song_player.song.repeat)
controller.queue_behavior(/datum/ai_behavior/setup_instrument, BB_SONG_DATUM, BB_SONG_LINES)
var/list/parsed_song_lines = splittext(controller.blackboard[BB_SONG_LINES], "\n")
popleft(parsed_song_lines) //remove BPM as it is parsed out
if(!compare_list(song_player.song.lines, parsed_song_lines) || !song_player.song.repeat)
controller.queue_behavior(/datum/ai_behavior/setup_instrument, BB_SONG_INSTRUMENT, BB_SONG_LINES)
if(!song_player.song.playing) //we may stop playing if we weren't playing before, were setting up dk theme, or ran out of repeats (also causing setup behavior)
controller.queue_behavior(/datum/ai_behavior/play_instrument, BB_SONG_DATUM)
controller.queue_behavior(/datum/ai_behavior/play_instrument, BB_SONG_INSTRUMENT)
/**
* 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/SelectBehaviors(datum/ai_controller/controller, delta_time)
var/mob/living/living_pawn = controller.pawn
if(SHOULD_RESIST(living_pawn) && DT_PROB(RESIST_SUBTREE_PROB, delta_time))
controller.queue_behavior(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO
return SUBTREE_RETURN_FINISH_PLANNING //IM NOT DOING ANYTHING ELSE BUT EXTUINGISH 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/SelectBehaviors(datum/ai_controller/controller, delta_time)
//inits the blackboard timer
if(!controller.blackboard[BB_NEXT_HUNGRY])
controller.blackboard[BB_NEXT_HUNGRY] = world.time + rand(0, 30 SECONDS)
if(world.time < controller.blackboard[BB_NEXT_HUNGRY])
return
if(!controller.blackboard[BB_FOOD_TARGET])
controller.queue_behavior(/datum/ai_behavior/find_and_set/edible, BB_FOOD_TARGET, /obj/item, 2)
return
controller.queue_behavior(/datum/ai_behavior/drop_item)
controller.queue_behavior(/datum/ai_behavior/consume, BB_FOOD_TARGET, BB_NEXT_HUNGRY)
return SUBTREE_RETURN_FINISH_PLANNING
+19 -1
View File
@@ -139,7 +139,11 @@
/datum/ai_behavior/monkey_attack_mob
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration
/datum/ai_behavior/monkey_attack_mob/perform(delta_time, datum/ai_controller/controller)
/datum/ai_behavior/monkey_attack_mob/setup(datum/ai_controller/controller, target_key)
. = ..()
controller.current_movement_target = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
/datum/ai_behavior/monkey_attack_mob/perform(delta_time, datum/ai_controller/controller, target_key)
. = ..()
var/mob/living/target = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
@@ -288,3 +292,17 @@
enemies[your_enemy] = MONKEY_RECRUIT_HATED_AMOUNT
monkey_ai.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] = world.time + MONKEY_RECRUIT_COOLDOWN
finish_action(controller, TRUE)
/datum/ai_behavior/monkey_set_combat_target/perform(delta_time, 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 || (!enemies[possible_enemy] && (!controller.blackboard[BB_MONKEY_AGGRESSIVE] || 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(controller.pawn, possible_enemy) || 1), 1)
if(!valids.len)
finish_action(controller, FALSE)
controller.blackboard[set_key] = pickweight(valids)
finish_action(controller, TRUE)
+7 -54
View File
@@ -7,9 +7,11 @@ have ways of interacting with a specific mob and control it.
/datum/ai_controller/monkey
movement_delay = 0.4 SECONDS
planning_subtrees = list(
/datum/ai_planning_subtree/monkey_tree,
//the monkey tree is mostly combat related, if it isn't busy with that then it may do instrument related things
/datum/ai_planning_subtree/play_instrument,
/datum/ai_planning_subtree/generic_resist,
/datum/ai_planning_subtree/monkey_combat,
/datum/ai_planning_subtree/generic_hunger,
/datum/ai_planning_subtree/generic_play_instrument,
/datum/ai_planning_subtree/monkey_shenanigans,
)
blackboard = list(
BB_MONKEY_AGGRESSIVE = FALSE,
@@ -23,7 +25,6 @@ have ways of interacting with a specific mob and control it.
BB_MONKEY_CURRENT_ATTACK_TARGET = null,
BB_MONKEY_GUN_NEURONS_ACTIVATED = FALSE,
BB_MONKEY_GUN_WORKED = TRUE,
BB_MONKEY_NEXT_HUNGRY = 0,
BB_SONG_LINES = MONKEY_SONG,
)
var/static/list/loc_connections = list(
@@ -43,8 +44,6 @@ have ways of interacting with a specific mob and control it.
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)
@@ -58,7 +57,6 @@ 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)
AddComponent(/datum/component/connect_loc_behalf, new_pawn, loc_connections)
movement_delay = living_pawn.cached_multiplicative_slowdown
@@ -103,11 +101,11 @@ have ways of interacting with a specific mob and control it.
for(var/obj/item/item in oview(2, living_pawn))
nearby_items += item
weapon = GetBestWeapon(nearby_items, living_pawn.held_items)
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(human.held_items + weapon, living_pawn.held_items)
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
@@ -124,47 +122,6 @@ have ways of interacting with a specific mob and control it.
queue_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/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
///Reactive events to being hit
/datum/ai_controller/monkey/proc/retaliate(mob/living/L)
var/list/enemies = blackboard[BB_MONKEY_ENEMIES]
@@ -251,7 +208,3 @@ 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
+50 -90
View File
@@ -1,102 +1,62 @@
/datum/ai_planning_subtree/monkey_tree/SelectBehaviors(datum/ai_controller/monkey/controller, delta_time)
var/mob/living/living_pawn = controller.pawn
if(SHOULD_RESIST(living_pawn) && DT_PROB(MONKEY_RESIST_PROB, delta_time))
controller.queue_behavior(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO
return SUBTREE_RETURN_FINISH_PLANNING //IM NOT DOING ANYTHING ELSE BUT EXTUINGISH MYSELF, GOOD GOD HAVE MERCY.
var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES]
if(HAS_TRAIT(controller.pawn, TRAIT_PACIFISM)) //Not a pacifist? lets try some combat behavior.
return
var/mob/living/selected_enemy
if(length(enemies) || controller.blackboard[BB_MONKEY_AGGRESSIVE]) //We have enemies or are pissed
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] && (!controller.blackboard[BB_MONKEY_AGGRESSIVE] || 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)
if(selected_enemy)
if(!selected_enemy.stat) //He's up, get him!
if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle
controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
controller.queue_behavior(/datum/ai_behavior/monkey_flee)
return //I'm running fuck you guys
if(controller.TryFindWeapon()) //Getting a weapon is higher priority if im not fleeing.
return SUBTREE_RETURN_FINISH_PLANNING
controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
controller.current_movement_target = selected_enemy
if(controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time)
controller.queue_behavior(/datum/ai_behavior/recruit_monkeys)
controller.queue_behavior(/datum/ai_behavior/battle_screech/monkey)
controller.queue_behavior(/datum/ai_behavior/monkey_attack_mob)
return SUBTREE_RETURN_FINISH_PLANNING //Focus on this
else //He's down, can we disposal him?
var/obj/machinery/disposal/bodyDisposal = locate(/obj/machinery/disposal/) in view(MONKEY_ENEMY_VISION, living_pawn)
if(bodyDisposal)
controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
controller.blackboard[BB_MONKEY_TARGET_DISPOSAL] = bodyDisposal
controller.queue_behavior(/datum/ai_behavior/disposal_mob, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_TARGET_DISPOSAL)
return SUBTREE_RETURN_FINISH_PLANNING
/datum/ai_planning_subtree/monkey_shenanigans/SelectBehaviors(datum/ai_controller/monkey/controller, delta_time)
if(prob(5))
controller.queue_behavior(/datum/ai_behavior/use_in_hand)
if(selected_enemy || !DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time))
if(!DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time))
return
if(world.time >= controller.blackboard[BB_MONKEY_NEXT_HUNGRY])
var/list/food_candidates = list()
for(var/obj/item as anything in living_pawn.held_items)
if(!item || !controller.IsEdible(item))
continue
food_candidates += item
for(var/obj/item/candidate in oview(2, living_pawn))
if(!controller.IsEdible(candidate))
continue
food_candidates += candidate
if(length(food_candidates))
var/obj/item/best_held = controller.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)
controller.queue_behavior(/datum/ai_behavior/consume, pick(food_candidates))
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))
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)
controller.blackboard[BB_MONKEY_CURRENT_PRESS_TARGET] = target
controller.queue_behavior(/datum/ai_behavior/use_on_object, BB_MONKEY_CURRENT_PRESS_TARGET)
return
controller.queue_behavior(/datum/ai_behavior/use_on_object, BB_MONKEY_CURRENT_PRESS_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
if(prob(5) && (locate(/obj/item) in living_pawn.held_items))
var/list/possible_receivers = list()
for(var/mob/living/candidate in oview(2, controller.pawn))
possible_receivers += candidate
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)
return
if(length(possible_receivers))
controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET] = pick(possible_receivers)
controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET)
return
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/SelectBehaviors(datum/ai_controller/monkey/controller, delta_time)
var/mob/living/living_pawn = controller.pawn
var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES]
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
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)
return SUBTREE_RETURN_FINISH_PLANNING
var/mob/living/selected_enemy = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
if(!selected_enemy.stat) //He's up, get him!
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)
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
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,14 @@
/datum/ai_planning_subtree/cursed/SelectBehaviors(datum/ai_controller/controller, delta_time)
var/obj/item/item_pawn = controller.pawn
//make sure we have a target
var/mob/living/carbon/curse_target = controller.blackboard[BB_ITEM_TARGET]
if(curse_target && get_dist(curse_target, item_pawn) > ITEM_AGGRO_VIEW_RANGE)
controller.blackboard[BB_ITEM_TARGET] = null
return
if(!controller.blackboard[BB_ITEM_TARGET])
controller.queue_behavior(/datum/ai_behavior/find_and_set/item_target, BB_ITEM_TARGET, /mob/living/carbon, ITEM_AGGRO_VIEW_RANGE)
controller.queue_behavior(/datum/ai_behavior/item_move_close_and_attack/ghostly, BB_ITEM_TARGET, BB_ITEM_THROW_ATTEMPT_COUNT)
+55
View File
@@ -0,0 +1,55 @@
///This behavior is for obj/items, it is used to free themselves out of the hands of whoever is holding them
/datum/ai_behavior/item_escape_grasp
/datum/ai_behavior/item_escape_grasp/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/obj/item/item_pawn = controller.pawn
var/mob/item_holder = item_pawn.loc
if(!istype(item_holder))
finish_action(controller, FALSE) //We're no longer beind held. abort abort!!
item_pawn.visible_message(span_warning("[item_pawn] slips out of the hands of [item_holder]!"))
item_holder.dropItemToGround(item_pawn, TRUE)
finish_action(controller, TRUE)
///This behavior is for obj/items, it is used to move closer to a target and throw themselves towards them.
/datum/ai_behavior/item_move_close_and_attack
required_distance = 3
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
action_cooldown = 20
///Sound to use
var/attack_sound
///Max attempts to make
var/max_attempts = 3
/datum/ai_behavior/item_move_close_and_attack/setup(datum/ai_controller/controller, aggro_list_key, target_key, throw_count_key)
. = ..()
controller.current_movement_target = controller.blackboard[target_key]
/datum/ai_behavior/item_move_close_and_attack/perform(delta_time, datum/ai_controller/controller, aggro_list_key, target_key, throw_count_key)
. = ..()
var/obj/item/item_pawn = controller.pawn
var/atom/throw_target = controller.blackboard[target_key]
item_pawn.visible_message(span_warning("[item_pawn] hurls towards [throw_target]!"))
item_pawn.throw_at(throw_target, rand(4,5), 9)
playsound(item_pawn.loc, attack_sound, 100, TRUE)
controller.blackboard[throw_count_key]++
if(controller.blackboard[throw_count_key] >= max_attempts)
finish_action(controller, TRUE, target_key, throw_count_key)
/datum/ai_behavior/item_move_close_and_attack/finish_action(datum/ai_controller/controller, succeeded, aggro_list_key, target_key, throw_count_key)
. = ..()
var/atom/throw_target = controller.blackboard[target_key]
controller.blackboard -= target_key
controller.blackboard[throw_count_key] = 0
if(aggro_list_key)
var/list/aggro_list = controller.blackboard[aggro_list_key]
aggro_list[throw_target]--
/datum/ai_behavior/item_move_close_and_attack/ghostly
attack_sound = 'sound/items/haunted/ghostitemattack.ogg'
max_attempts = 4
+1
View File
@@ -214,6 +214,7 @@
#include "code\__HELPERS\_logging.dm"
#include "code\__HELPERS\_string_lists.dm"
#include "code\__HELPERS\admin.dm"
#include "code\__HELPERS\ai.dm"
#include "code\__HELPERS\areas.dm"
#include "code\__HELPERS\atmospherics.dm"
#include "code\__HELPERS\atoms.dm"