mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 06:00:16 +01:00
[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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user