diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index b5a7ad1ddfa..f7f77a7169e 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -195,6 +195,14 @@ #define BB_DRILLABLE_ICE "BB_drillable_ice" +//emotions we displays depending on our happiness +///emotions we display when happy +#define BB_HAPPY_EMOTIONS "happy_emotions" +///emotions we display when neutral +#define BB_MODERATE_EMOTIONS "moderate_emotions" +///emotions we display when depressed +#define BB_SAD_EMOTIONS "sad_emotions" + // Keys used by one and only one behavior // Used to hold state without making bigass lists /// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 330e2d48eb2..d77817a2039 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -304,3 +304,11 @@ #define BB_DEER_RESTING "deer_resting" ///time till our next rest duration #define BB_DEER_NEXT_REST_TIMER "deer_next_rest_timer" + +//turtle +///our tree's ability +#define BB_TURTLE_TREE_ABILITY "turtle_tree_ability" +///people we headbutt! +#define BB_TURTLE_HEADBUTT_VICTIM "turtle_headbutt_victim" +///flore we must smell +#define BB_TURTLE_FLORA_TARGET "turtle_flora_target" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm index 6cae6132d36..4d7a3e7ad30 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm @@ -31,11 +31,11 @@ var/list/final_list switch(happiness_value) if(HIGH_HAPPINESS_THRESHOLD to INFINITY) - final_list = happy_emotions + final_list = controller.blackboard[BB_HAPPY_EMOTIONS] || happy_emotions if(MODERATE_HAPPINESS_THRESHOLD to HIGH_HAPPINESS_THRESHOLD) - final_list = moderate_emotions + final_list = controller.blackboard[BB_MODERATE_EMOTIONS] || moderate_emotions else - final_list = depressed_emotions + final_list = controller.blackboard[BB_SAD_EMOTIONS] || depressed_emotions if(!length(final_list)) return controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(final_list)) diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm index 75caa272ef9..4be983b3211 100644 --- a/code/datums/elements/basic_eating.dm +++ b/code/datums/elements/basic_eating.dm @@ -46,8 +46,10 @@ SIGNAL_HANDLER if(user.combat_mode || !is_type_in_list(possible_food, food_types)) return NONE - - try_eating(source, possible_food, user) + var/mob/living/living_source = source + if(living_source.stat != CONSCIOUS) + return NONE + return try_eating(source, possible_food, user) ? ITEM_INTERACT_SUCCESS : NONE /datum/element/basic_eating/proc/on_unarm_attack(mob/living/eater, atom/target, proximity, modifiers) SIGNAL_HANDLER diff --git a/code/modules/cargo/packs/livestock.dm b/code/modules/cargo/packs/livestock.dm index 62008d8be48..da51ee497f6 100644 --- a/code/modules/cargo/packs/livestock.dm +++ b/code/modules/cargo/packs/livestock.dm @@ -259,3 +259,10 @@ cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/storage/fish_case/tiziran = 2) crate_name = "tiziran fish crate" + +/datum/supply_pack/critter/turtle + name = "Turtle Crate" + desc = "Cute flora turtles that'll emit good vibes to nearby plants!" + cost = CARGO_CRATE_VALUE * 2 + contains = list(/mob/living/basic/turtle) + crate_name = "flora-turtle crate" diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index abf3a298462..cb0eaecc989 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -367,7 +367,10 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) final_table[result] *= rod.hook.get_hook_bonus_multiplicative(result) final_table[result] += rod.hook.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative - if(ispath(result, /obj/item/fish) || isfish(result)) + if(ispath(result, /mob/living) && bait && (HAS_TRAIT(bait, TRAIT_GOOD_QUALITY_BAIT) || HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT))) + final_table[result] = round(final_table[result] * result_multiplier, 1) + + else if(ispath(result, /obj/item/fish) || isfish(result)) if(bait) final_table[result] = round(final_table[result] * result_multiplier, 1) var/mult = bait.check_bait(result) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index 6f2a38d4d61..81776519735 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -507,6 +507,7 @@ /obj/item/seeds/random = 1, /mob/living/basic/frog = 1, /mob/living/basic/axolotl = 1, + /mob/living/basic/turtle = 2, ) fish_counts = list( /obj/item/food/grown/grass = 10, diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index c8a3e10b8e9..ac2e0e3b767 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -559,7 +559,7 @@ if(weedlevel == new_weedlevel) return SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_WEEDLEVEL, new_weedlevel) - weedlevel = new_weedlevel + weedlevel = max(new_weedlevel, 0) if(update_icon) update_appearance() diff --git a/code/modules/mob/living/basic/turtle/turtle.dm b/code/modules/mob/living/basic/turtle/turtle.dm new file mode 100644 index 00000000000..4d7c73edec6 --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle.dm @@ -0,0 +1,240 @@ +#define PATH_PEST_KILLER "path_pest_killer" +#define PATH_PLANT_HEALER "path_plant_healer" +#define PATH_PLANT_MUTATOR "path_plant_mutator" +#define REQUIRED_TREE_GROWTH 250 +#define UPPER_BOUND_VOLUME 50 +#define LOWER_BOUND_VOLUME 10 + +/mob/living/basic/turtle + name = "turtle" + desc = "Dog." + icon_state = "turtle" + icon_living = "turtle" + icon_dead = "turtle_dead" + base_icon_state = "turtle" + icon = 'icons/mob/simple/pets.dmi' + butcher_results = list(/obj/item/food/meat/slab = 3, /obj/item/food/pickle = 1, /obj/item/stack/sheet/mineral/wood = 10) + mob_biotypes = MOB_ORGANIC | MOB_PLANT + mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT + health = 100 + maxHealth = 100 + speed = 5 + verb_say = "snaps" + verb_ask = "snaps curiously" + verb_exclaim = "snaps loudly" + verb_yell = "snaps loudly" + faction = list(FACTION_NEUTRAL) + ai_controller = /datum/ai_controller/basic_controller/turtle + ///our displayed tree + var/mutable_appearance/grown_tree + ///growth progress of our tree + var/list/path_growth_progress = list( + PATH_PLANT_HEALER = 0, + PATH_PLANT_MUTATOR = 0, + PATH_PEST_KILLER = 0, + ) + ///what nutrients leads to each evolution path + var/static/list/path_requirements = list( + //plant healers + /datum/reagent/plantnutriment/eznutriment = PATH_PLANT_HEALER, + /datum/reagent/plantnutriment/robustharvestnutriment = PATH_PLANT_HEALER, + /datum/reagent/plantnutriment/endurogrow = PATH_PLANT_HEALER, + //plant mutators + /datum/reagent/plantnutriment/left4zednutriment = PATH_PLANT_MUTATOR, + /datum/reagent/uranium = PATH_PLANT_MUTATOR, + //pest killers + /datum/reagent/toxin/pestkiller = PATH_PEST_KILLER, + ) + ///if we are fully grown, what is our path + var/developed_path + ///our last east/west direction + var/last_direction = WEST + +/mob/living/basic/turtle/Initialize(mapload) + . = ..() + + desc = pick( + "Likely Dog...", + "Praise the Dog!", + "Dog ahead.", + "Could this be a Dog?", + ) + var/static/list/eatable_food = list(/obj/item/seeds) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(eatable_food)) + AddElement(/datum/element/basic_eating, food_types = eatable_food) + AddComponent(/datum/component/happiness) + RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(pre_eat_food)) + update_appearance() + create_reagents(150, REAGENT_HOLDER_ALIVE) + add_verb(src, /mob/living/proc/toggle_resting) + START_PROCESSING(SSprocessing, src) + +/mob/living/basic/turtle/setDir(newdir) + if(REVERSE_DIR(last_direction) & newdir) + transform = transform.Scale(-1, 1) + last_direction = REVERSE_DIR(last_direction) + return ..() + +/mob/living/basic/turtle/proc/retrieve_destined_path() + var/current_max_growth = 0 + var/destined_path + for(var/evolution_path in path_growth_progress) + if(path_growth_progress[evolution_path] > current_max_growth) + destined_path = evolution_path + current_max_growth = path_growth_progress[evolution_path] + if(isnull(destined_path)) + destined_path = PATH_PLANT_HEALER + return destined_path + +/mob/living/basic/turtle/process(seconds_per_tick) + if(isnull(reagents) || !length(reagents.reagent_list)) //if we have no reagents, default to our highest destined path + set_plant_growth(retrieve_destined_path(), 0.5) + return + + for(var/datum/reagent/existing_reagent as anything in reagents.reagent_list) + var/evolution_path = path_requirements[existing_reagent.type] + + switch(existing_reagent.volume) + if(UPPER_BOUND_VOLUME to INFINITY) + set_plant_growth(evolution_path, 3) + if(LOWER_BOUND_VOLUME to UPPER_BOUND_VOLUME) + set_plant_growth(evolution_path, 2) + if(1 to LOWER_BOUND_VOLUME) + set_plant_growth(evolution_path, 1) + + reagents.remove_reagent(existing_reagent.type, 0.5) + +/mob/living/basic/turtle/proc/set_plant_growth(evolution_path, amount) + path_growth_progress[evolution_path] += amount + if(path_growth_progress[evolution_path] >= REQUIRED_TREE_GROWTH) + evolve_turtle(evolution_path) + +/mob/living/basic/turtle/examine(mob/user) + . = ..() + + if(stat == DEAD) + . += span_notice("Its tree seems to be all withered...") + return + + var/destined_path = retrieve_destined_path() + var/current_max_growth = path_growth_progress[destined_path] + + var/text_to_display = "Its tree seems to be exuding " + switch(destined_path) + if(PATH_PEST_KILLER) + text_to_display += "pest killing" + if(PATH_PLANT_HEALER) + text_to_display += "plant healing" + if(PATH_PLANT_MUTATOR) + text_to_display += "plant mutating" + + text_to_display += " properties... which [current_max_growth >= REQUIRED_TREE_GROWTH ? "seems to be fully grown" : "is yet to develop"]." + . += span_notice(text_to_display) + + +/mob/living/basic/turtle/proc/evolve_turtle(evolution_path) + var/static/list/evolution_gains = list( + PATH_PLANT_HEALER = list( + "tree_appearance" = "healer_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/healer, + ), + PATH_PEST_KILLER = list( + "tree_appearance" = "killer_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/killer, + ), + PATH_PLANT_MUTATOR = list( + "tree_appearance" = "mutator_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/mutator, + ), + ) + + var/tree_icon_state = evolution_gains[evolution_path]["tree_appearance"] + grown_tree = mutable_appearance(icon = 'icons/mob/simple/turtle_trees.dmi', icon_state = tree_icon_state) + + var/new_ability_path = evolution_gains[evolution_path]["tree_ability"] + developed_path = evolution_path + var/datum/action/cooldown/tree_ability = new new_ability_path(src) + tree_ability?.Grant(src) + ai_controller?.set_blackboard_key(BB_TURTLE_TREE_ABILITY, tree_ability) + STOP_PROCESSING(SSprocessing, src) + update_appearance() + +/mob/living/basic/turtle/update_icon_state() + . = ..() + if(stat == DEAD) + return + icon_state = resting ? "[base_icon_state]_resting" : base_icon_state + +/mob/living/basic/turtle/update_overlays() + . = ..() + if(stat == DEAD) + var/mutable_appearance/dead_overlay = mutable_appearance(icon = 'icons/mob/simple/pets.dmi', icon_state = developed_path ? "dead_tree" : "growing_tree") + dead_overlay.pixel_y = -2 + . += dead_overlay + return + var/pixel_offset = resting ? -2 : 2 + var/mutable_appearance/living_tree = grown_tree ? grown_tree : mutable_appearance(icon = icon, icon_state = "growing_tree") + living_tree.pixel_y = pixel_offset + . += living_tree + +/mob/living/basic/turtle/update_resting() + . = ..() + if(stat == DEAD) + return + update_appearance() + +/mob/living/basic/turtle/item_interaction(mob/living/user, obj/item/used_item, list/modifiers) + if(!istype(used_item, /obj/item/reagent_containers)) + return NONE + + if(isnull(used_item.reagents)) + balloon_alert(user, "empty!") + return ITEM_INTERACT_SUCCESS + + if(stat == DEAD) + balloon_alert(user, "its dead!") + return ITEM_INTERACT_SUCCESS + + var/should_transfer = FALSE + for(var/reagent in path_requirements) + if(used_item.reagents.has_reagent(reagent)) + should_transfer = TRUE + break + + if(!should_transfer) + balloon_alert(user, "refuses to drink!") + return ITEM_INTERACT_SUCCESS + + if(!do_after(user, 1.5 SECONDS, target = src)) + return ITEM_INTERACT_SUCCESS + + used_item.reagents.trans_to(reagents, 5) + balloon_alert(user, "drinks happily") + playsound(src, 'sound/items/drink.ogg', vol = 25, vary = TRUE) + return ITEM_INTERACT_SUCCESS + +/mob/living/basic/turtle/proc/pre_eat_food(datum/source, obj/item/seeds/potential_food) + SIGNAL_HANDLER + + if(!istype(potential_food)) + return NONE + if(ispath(potential_food.product, /obj/item/food/grown)) + addtimer(CALLBACK(src, PROC_REF(process_food), potential_food.product), 30 SECONDS) + return NONE + +/mob/living/basic/turtle/proc/process_food(food_path) + if(QDELETED(src) || stat != CONSCIOUS) + return + new food_path(drop_location()) + balloon_alert_to_viewers("spits out some food") + +/mob/living/basic/turtle/death(gibbed) + . = ..() + STOP_PROCESSING(SSprocessing, src) + +#undef PATH_PEST_KILLER +#undef PATH_PLANT_HEALER +#undef PATH_PLANT_MUTATOR +#undef REQUIRED_TREE_GROWTH +#undef UPPER_BOUND_VOLUME +#undef LOWER_BOUND_VOLUME diff --git a/code/modules/mob/living/basic/turtle/turtle_ability.dm b/code/modules/mob/living/basic/turtle/turtle_ability.dm new file mode 100644 index 00000000000..3c3172b8cd5 --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle_ability.dm @@ -0,0 +1,140 @@ +#define TREE_FIELD_DURATION_EFFECT 10 SECONDS +#define WARP_ANIMATE_TIME 0.35 SECONDS + +/datum/action/cooldown/mob_cooldown/turtle_tree + name = "Tree Ability" + desc = "Invoke your tree's special ability." + cooldown_time = 2 MINUTES + click_to_activate = FALSE + button_icon = 'icons/mob/simple/pets.dmi' + button_icon_state = "turtle" + ///type of effect our tree releases + var/effect_path + ///how many times our ability affects surroundings + var/maximum_intervals = 5 + ///time between each interval + var/time_between_intervals = 3 SECONDS + ///range our tree affects + var/tree_range = 5 + ///warp effect to apply some distortion to our field + var/atom/movable/warp_effect/turtle_field/warp + +/datum/action/cooldown/mob_cooldown/turtle_tree/Activate(atom/target) + . = ..() + warp = new(owner) + RegisterSignal(warp, COMSIG_QDELETING, PROC_REF(remove_warp)) + warp.alpha = 0 + owner.vis_contents += warp + + for(var/index in 0 to maximum_intervals) + addtimer(CALLBACK(src, PROC_REF(tree_effect)), time_between_intervals * index) + + animate(warp, transform = matrix(), alpha = warp::alpha, time = WARP_ANIMATE_TIME) + addtimer(CALLBACK(src, PROC_REF(warp_extinguish)), (time_between_intervals * maximum_intervals) + 3 SECONDS) + +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/warp_extinguish() + if(QDELETED(warp)) + return + animate(warp, alpha = 0, time = WARP_ANIMATE_TIME) + addtimer(CALLBACK(src, PROC_REF(remove_warp)), WARP_ANIMATE_TIME) + +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/remove_warp() + SIGNAL_HANDLER + + UnregisterSignal(warp, COMSIG_QDELETING) + warp = null + +///effect we apply on our trees +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/tree_effect() + SHOULD_CALL_PARENT(TRUE) + return (pre_effect_apply()) + +///things we should check for before applying our effects +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/pre_effect_apply() + if(QDELETED(owner) || owner.stat == DEAD) + return FALSE + var/obj/effect/tree_effect = new effect_path + owner.vis_contents += tree_effect + return TRUE + +///healer tree, heals nearby plants by small amounts +/datum/action/cooldown/mob_cooldown/turtle_tree/healer + effect_path = /obj/effect/temp_visual/circle_wave/tree/healer + ///amount we heal plants by + var/heal_amount = 5 + +/datum/action/cooldown/mob_cooldown/turtle_tree/healer/tree_effect() + . = ..() + if(!.) + return + for(var/obj/machinery/hydroponics/hydro in oview(tree_range, owner)) + if(isnull(hydro.myseed)) + continue + hydro.adjust_plant_health(heal_amount) + +///killer tree, kills plant's pests and weeds, aswell as nearby vermin +/datum/action/cooldown/mob_cooldown/turtle_tree/killer + effect_path = /obj/effect/temp_visual/circle_wave/tree/killer + ///amount we heal plants by + var/vermin_damage_amount = 20 + ///type of vermin our field affects + var/static/list/vermin_mob_targets = typecacheof(list( + /mob/living/basic/cockroach, + /mob/living/basic/mouse/rat, + )) + ///how much we reduce weed levels + var/weed_level_reduce = 2 + +/datum/action/cooldown/mob_cooldown/turtle_tree/killer/tree_effect() + . = ..() + if(!.) + return + + for(var/atom/possible_target as anything in oview(tree_range, owner)) + + if(is_type_in_typecache(possible_target, vermin_mob_targets)) + var/mob/living/living_target = possible_target + living_target.apply_damage(vermin_damage_amount) + continue + + if(!istype(possible_target, /obj/machinery/hydroponics)) + continue + + var/obj/machinery/hydroponics/hydro = possible_target + if(isnull(hydro.myseed)) + continue + hydro.set_weedlevel(hydro.weedlevel - weed_level_reduce) + +///mutator tree, mutates nearby plants! +/datum/action/cooldown/mob_cooldown/turtle_tree/mutator + effect_path = /obj/effect/temp_visual/circle_wave/tree/mutator + ///how much we mutate plants + var/mutator_boost = 1 + +/datum/action/cooldown/mob_cooldown/turtle_tree/mutator/tree_effect() + . = ..() + if(!.) + return + for(var/obj/machinery/hydroponics/hydro in oview(tree_range, owner)) + hydro.myseed?.adjust_instability(mutator_boost) + +/atom/movable/warp_effect/turtle_field + alpha = 75 + +///effects we give our tree abilities depending on their type +/obj/effect/temp_visual/circle_wave/tree + vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_PLANE + duration = 10 SECONDS + amount_to_scale = 3 + +/obj/effect/temp_visual/circle_wave/tree/healer + color = "#28a3bc" + +/obj/effect/temp_visual/circle_wave/tree/killer + color = "#ce3ebf" + +/obj/effect/temp_visual/circle_wave/tree/mutator + color = "#c49f26" + +#undef TREE_FIELD_DURATION_EFFECT +#undef WARP_ANIMATE_TIME diff --git a/code/modules/mob/living/basic/turtle/turtle_ai.dm b/code/modules/mob/living/basic/turtle/turtle_ai.dm new file mode 100644 index 00000000000..1af7c3111f7 --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle_ai.dm @@ -0,0 +1,92 @@ +/datum/ai_controller/basic_controller/turtle + blackboard = list( + BB_HAPPY_EMOTIONS = list( + "wiggles its tree in excitement!", + "raises its head up high!", + "wags its tail enthusiastically!", + ), + BB_MODERATE_EMOTIONS = list( + "keeps its head level, eyes half-closed.", + "basks in the light peacefully.", + ), + BB_SAD_EMOTIONS = list( + "looks towards the floor in dissapointment...", + "the leaves on its tree droop...", + ), + ) + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking + planning_subtrees = list( + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/express_happiness, + /datum/ai_planning_subtree/use_mob_ability/turtle_tree, + /datum/ai_planning_subtree/find_and_hunt_target/headbutt_people, //playfully headbutt people's legs + /datum/ai_planning_subtree/find_and_hunt_target/sniff_flora, //mmm the aroma + ) + +/datum/ai_planning_subtree/use_mob_ability/turtle_tree + ability_key = BB_TURTLE_TREE_ABILITY + +/datum/ai_planning_subtree/use_mob_ability/turtle_tree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/happiness_count = controller.blackboard[BB_BASIC_HAPPINESS] * 100 + if(happiness_count > 75) + return ..() + if(!SPT_PROB(happiness_count / 50, seconds_per_tick)) + return + return ..() + +/datum/ai_planning_subtree/find_and_hunt_target/sniff_flora + target_key = BB_TURTLE_FLORA_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/sniff_flora + hunting_behavior = /datum/ai_behavior/hunt_target/sniff_flora + hunt_targets = list( + /obj/machinery/hydroponics, + /obj/item/kirbyplants, + ) + hunt_range = 5 + hunt_chance = 45 + +/datum/ai_behavior/find_hunt_target/sniff_flora + action_cooldown = 1 MINUTES + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_hunt_target/sniff_flora/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius, datum/ai_controller/controller, seconds_per_tick) + if(!istype(dinner)) + return TRUE + if(isnull(dinner.myseed)) + return FALSE + if(dinner.weedlevel > 5 || dinner.pestlevel > 5) //too smelly + return FALSE + return can_see(source, dinner, radius) + +/datum/ai_behavior/hunt_target/sniff_flora + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/sniff_flora/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("Enjoys the sweet scent eminating from [hunted::name]!") + +/datum/ai_planning_subtree/find_and_hunt_target/headbutt_people + target_key = BB_TURTLE_HEADBUTT_VICTIM + finding_behavior = /datum/ai_behavior/find_hunt_target/human_to_headbutt + hunting_behavior = /datum/ai_behavior/hunt_target/headbutt_leg + hunt_targets = list(/mob/living/carbon/human) + hunt_range = 4 + hunt_chance = 45 + +/datum/ai_behavior/find_hunt_target/human_to_headbutt + action_cooldown = 2 MINUTES + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_hunt_target/human_to_headbutt/valid_dinner(mob/living/source, mob/living/carbon/human/dinner, radius, datum/ai_controller/controller, seconds_per_tick) + if(dinner.stat != CONSCIOUS) + return FALSE + if(isnull(dinner.get_bodypart(BODY_ZONE_R_LEG)) && isnull(dinner.get_bodypart(BODY_ZONE_L_LEG))) //no legs to headbutt! + return FALSE + return can_see(source, dinner, radius) + +/datum/ai_behavior/hunt_target/headbutt_leg + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/headbutt_leg/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("playfully headbutts [hunted]'s legs!") + diff --git a/icons/mob/simple/pets.dmi b/icons/mob/simple/pets.dmi index b7e86423872..512a48e813b 100644 Binary files a/icons/mob/simple/pets.dmi and b/icons/mob/simple/pets.dmi differ diff --git a/icons/mob/simple/turtle_trees.dmi b/icons/mob/simple/turtle_trees.dmi new file mode 100644 index 00000000000..3e5cf1d4065 Binary files /dev/null and b/icons/mob/simple/turtle_trees.dmi differ diff --git a/tgstation.dme b/tgstation.dme index bec1ca1f5a4..269325a558b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5162,6 +5162,9 @@ #include "code\modules\mob\living\basic\trooper\syndicate.dm" #include "code\modules\mob\living\basic\trooper\trooper.dm" #include "code\modules\mob\living\basic\trooper\trooper_ai.dm" +#include "code\modules\mob\living\basic\turtle\turtle.dm" +#include "code\modules\mob\living\basic\turtle\turtle_ability.dm" +#include "code\modules\mob\living\basic\turtle\turtle_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" #include "code\modules\mob\living\basic\vermin\butterfly.dm" #include "code\modules\mob\living\basic\vermin\cockroach.dm"