diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index 289f75862229..fe8e9541bf33 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -83,3 +83,5 @@ #define BB_MOD_IMPLANT "BB_mod_implant" ///Range for a MOD AI controller. #define MOD_AI_RANGE 200 + +#define BB_GROUP_DATUM "BB_group_datum" diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm index f941b2eb95d5..70a557ccc436 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm @@ -9,22 +9,24 @@ /// How far ahead do we plot movement per action? Further means longer until we return to the decision tree, fewer means jerkier movement /// This can still result in long moves because this is "a tile x tiles away" not "only move x tiles", you might path around some walls var/step_distance = 3 + var/overwrites_main = FALSE /datum/ai_behavior/step_towards_turf/setup(datum/ai_controller/controller, turf_key) - var/turf/target_turf = controller.blackboard[turf_key] - if (QDELETED(target_turf) || target_turf.is_blocked_turf(exclude_mobs = TRUE)) - target_turf = find_destination_turf(args) - if (!target_turf) - return FALSE - controller.set_blackboard_key(turf_key, target_turf) + if(!overwrites_main) + var/turf/target_turf = controller.blackboard[turf_key] + if (QDELETED(target_turf) || target_turf.is_blocked_turf(exclude_mobs = TRUE)) + target_turf = find_destination_turf(args) + if (!target_turf) + return FALSE + controller.set_blackboard_key(turf_key, target_turf) - if (target_turf.z != controller.pawn.z) - return FALSE + if (target_turf.z != controller.pawn.z) + return FALSE - var/turf/destination = plot_movement(controller, target_turf) - if (!destination) - return FALSE - set_movement_target(controller, destination) + var/turf/destination = plot_movement(controller, target_turf) + if (!destination) + return FALSE + set_movement_target(controller, destination) return ..() /** diff --git a/monkestation/code/modules/ocean_content/icons/fish.dmi b/monkestation/code/modules/ocean_content/icons/fish.dmi new file mode 100644 index 000000000000..028d52fdc2d5 Binary files /dev/null and b/monkestation/code/modules/ocean_content/icons/fish.dmi differ diff --git a/monkestation/code/modules/ocean_content/mobs/ai/behaviours/attempt_group_find.dm b/monkestation/code/modules/ocean_content/mobs/ai/behaviours/attempt_group_find.dm new file mode 100644 index 000000000000..9d0f176f7fd7 --- /dev/null +++ b/monkestation/code/modules/ocean_content/mobs/ai/behaviours/attempt_group_find.dm @@ -0,0 +1,24 @@ +/datum/ai_behavior/attempt_group_find + var/group_to_find = /datum/group_planning + +/datum/ai_behavior/attempt_group_find/perform(seconds_per_tick, datum/ai_controller/controller, ...) + . = ..() + for(var/mob/living/basic/found_basic in view(7, controller.pawn)) + if(BB_GROUP_DATUM in found_basic.ai_controller.blackboard) + var/datum/group_planning/found_group = found_basic.ai_controller.blackboard[BB_GROUP_DATUM] + if(!found_group) + continue + if(found_group.type != group_to_find) + continue + controller.blackboard[BB_GROUP_DATUM] = found_group + found_group.group_mobs |= controller.pawn + finish_action(controller, TRUE) + break + + var/datum/group_planning/new_group = new group_to_find + controller.blackboard[BB_GROUP_DATUM] = new_group + new_group.group_mobs |= controller.pawn + finish_action(controller, TRUE) + +/datum/ai_behavior/attempt_group_find/fish + group_to_find = /datum/group_planning/fish diff --git a/monkestation/code/modules/ocean_content/mobs/ai/behaviours/group_move_towards.dm b/monkestation/code/modules/ocean_content/mobs/ai/behaviours/group_move_towards.dm new file mode 100644 index 000000000000..8cd5218651f0 --- /dev/null +++ b/monkestation/code/modules/ocean_content/mobs/ai/behaviours/group_move_towards.dm @@ -0,0 +1,28 @@ +/datum/ai_behavior/step_towards_turf/group_movement + overwrites_main = TRUE + +/datum/ai_behavior/step_towards_turf/group_movement/setup(datum/ai_controller/controller, turf_key) + var/datum/group_planning/listed_group = controller.blackboard[BB_GROUP_DATUM] + var/turf/target_turf = listed_group.target + + if(!target_turf) + listed_group.decide_next_action() + target_turf = listed_group.target + if(!target_turf) + return FALSE + + if (target_turf.z != controller.pawn.z) + return FALSE + + var/turf/destination = plot_movement(controller, target_turf) + if (!destination) + return FALSE + set_movement_target(controller, destination) + return ..() + + +/datum/ai_behavior/step_towards_turf/group_movement/finish_action(datum/ai_controller/controller, succeeded, ...) + . = ..() + var/datum/group_planning/listed_group = controller.blackboard[BB_GROUP_DATUM] + if(listed_group) + listed_group.finish_action(controller) diff --git a/monkestation/code/modules/ocean_content/mobs/ai/controller.dm b/monkestation/code/modules/ocean_content/mobs/ai/controller.dm new file mode 100644 index 000000000000..b4b0bc58f519 --- /dev/null +++ b/monkestation/code/modules/ocean_content/mobs/ai/controller.dm @@ -0,0 +1,10 @@ +/datum/ai_controller/basic_controller/fish + blackboard = list( + BB_GROUP_DATUM = null + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_create_or_follow_commands/fish, + ) diff --git a/monkestation/code/modules/ocean_content/mobs/ai/subtrees/simple_try_attach_group.dm b/monkestation/code/modules/ocean_content/mobs/ai/subtrees/simple_try_attach_group.dm new file mode 100644 index 000000000000..cf52ae6d8ebd --- /dev/null +++ b/monkestation/code/modules/ocean_content/mobs/ai/subtrees/simple_try_attach_group.dm @@ -0,0 +1,24 @@ +/datum/ai_planning_subtree/simple_create_or_follow_commands + var/group_finding_behaviour = /datum/ai_behavior/attempt_group_find + +/datum/ai_planning_subtree/simple_create_or_follow_commands/fish + group_finding_behaviour = /datum/ai_behavior/attempt_group_find/fish + + +/datum/ai_planning_subtree/simple_create_or_follow_commands/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/group_planning/attached = controller.blackboard[BB_GROUP_DATUM] + if(!attached) + controller.queue_behavior(group_finding_behaviour) + return + + if(length(attached.in_progress_mobs) && !(controller.pawn in attached.in_progress_mobs) && !(controller.pawn in attached.finished_mobs)) + attached.add_to_current_action(controller) + return + + if(!attached.next_action > world.time && !length(attached.in_progress_mobs)) + return + + if(!attached.fetched_behaviour) + attached.decide_next_action() + attached.bulk_queue() + diff --git a/monkestation/code/modules/ocean_content/mobs/fish_base.dm b/monkestation/code/modules/ocean_content/mobs/fish_base.dm new file mode 100644 index 000000000000..d592a19bd47b --- /dev/null +++ b/monkestation/code/modules/ocean_content/mobs/fish_base.dm @@ -0,0 +1,18 @@ +/mob/living/basic/aquatic/fish + icon = 'monkestation/code/modules/ocean_content/icons/fish.dmi' + icon_state = "fish" + icon_living = "fish" + icon_dead = "fish_dead" + icon_gib = "fish_dead" + + mob_size = MOB_SIZE_SMALL + faction = list(FACTION_CARP) + speak_emote = list("glubs") + + habitable_atmos = list("min_oxy" = 2, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 1200 + + ai_controller = /datum/ai_controller/basic_controller/fish + + diff --git a/monkestation/code/modules/ocean_content/mobs/group_planning.dm b/monkestation/code/modules/ocean_content/mobs/group_planning.dm new file mode 100644 index 000000000000..b65c54d713ca --- /dev/null +++ b/monkestation/code/modules/ocean_content/mobs/group_planning.dm @@ -0,0 +1,68 @@ +/datum/group_planning + ///when our next queuing is done + var/next_action = 0 + ///our cooldown time of actions + var/cooldown_time = 10 SECONDS + ///list of mobs in our group + var/list/group_mobs = list() + ///list of mobs still executing our queued_behavior + var/list/in_progress_mobs = list() + ///list of finished mobs + var/list/finished_mobs = list() + ///our behaviour that we are queing + var/queued_behavior + ///do we need to fetch a new behaviour? + var/fetched_behaviour = FALSE + ///list of all behaviours we can do + var/list/usable_behaviours = list() + ///our current_target + var/atom/target + +/datum/group_planning/proc/bulk_queue() + for(var/mob/living/basic/listed as anything in group_mobs) + if(!istype(listed) || !listed.ai_controller || listed.stat == DEAD) //cull dead members that shouldn't exist anymore + if(isbasicmob(listed) && listed.ai_controller && (BB_GROUP_DATUM in listed.ai_controller.blackboard)) + listed.ai_controller.blackboard[BB_GROUP_DATUM] = null + group_mobs -= listed + continue + listed.ai_controller.queue_behavior(queued_behavior) + in_progress_mobs |= listed + +/datum/group_planning/proc/decide_next_action() + if(length(in_progress_mobs)) + return /// we are still doing an action + if(!length(usable_behaviours)) + return //how did this happen + queued_behavior = pick(usable_behaviours) + fetched_behaviour = TRUE + +/datum/group_planning/proc/add_to_current_action(datum/ai_controller/controller) + controller.queue_behavior(queued_behavior) + in_progress_mobs |= controller.pawn + + +/datum/group_planning/proc/finish_action(datum/ai_controller/controller) + if(controller.pawn in in_progress_mobs) + in_progress_mobs -= controller.pawn + finished_mobs += controller.pawn + if(!length(in_progress_mobs)) + next_action = world.time + cooldown_time + fetched_behaviour = FALSE + finished_mobs = list() + +/datum/group_planning/fish + cooldown_time = 25 SECONDS + usable_behaviours = list(/datum/ai_behavior/step_towards_turf/group_movement) + +/datum/group_planning/fish/decide_next_action() + . = ..() + var/mob/living/basic/picked_mob = pick(group_mobs) + + var/list/turfs = view(7, picked_mob) + var/turf/picked + var/sanity = 25 + + while(!isopenturf(picked) && sanity > 0) + sanity-- + picked = get_turf(pick(turfs)) + target = picked diff --git a/tgstation.dme b/tgstation.dme index 287bc5821cff..c6849d09446e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5976,6 +5976,12 @@ #include "monkestation\code\modules\ocean_content\hotspot_machines\dousing_rod.dm" #include "monkestation\code\modules\ocean_content\hotspot_machines\hotspot_vents.dm" #include "monkestation\code\modules\ocean_content\hotspot_machines\stomper.dm" +#include "monkestation\code\modules\ocean_content\mobs\fish_base.dm" +#include "monkestation\code\modules\ocean_content\mobs\group_planning.dm" +#include "monkestation\code\modules\ocean_content\mobs\ai\controller.dm" +#include "monkestation\code\modules\ocean_content\mobs\ai\behaviours\attempt_group_find.dm" +#include "monkestation\code\modules\ocean_content\mobs\ai\behaviours\group_move_towards.dm" +#include "monkestation\code\modules\ocean_content\mobs\ai\subtrees\simple_try_attach_group.dm" #include "monkestation\code\modules\outdoors\code\admin_commands.dm" #include "monkestation\code\modules\outdoors\code\misc_procs.dm" #include "monkestation\code\modules\outdoors\code\new_vars.dm"