mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-31 09:08:30 +01:00
basic cats and mini kitchen helpers (#79800)
## About The Pull Request this pr transforms cats into basic pets! cats now have some new behavior. they can carry fish and hunted mice in their mouths to deliver it to kittens, and kittens will eat them.   if a kitten sees you holding food, it will point at you and meow loudly until u give it the food. becareful when putting male cats near each other, there is a small chance they get into a heated argument and meow loudly at each other until one of them flees. also added a new small cat house for cats. cats will use these homes if u build one near them (using 5 wood planks)  Chefs can craft the cake cat and breadcat. these are useful cats because they can help the chef around in the kitchen. they will turn stoves and grills off when food is ready, so they dont burn. and the cake cat will help the chef decorate his donuts ## Why It's Good For The Game refactors cats into basic mobs and gives them a deeper ai ## Changelog 🆑 refactor: cats are now basic pets. please report any bugs. add: the cake cat and bread cat can now help the chef around in the kitchen /🆑
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/datum/ai_controller/basic_controller/cat/bread
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food,
|
||||
/datum/ai_planning_subtree/haul_food_to_young,
|
||||
/datum/ai_planning_subtree/random_speech/cats,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove
|
||||
target_key = BB_STOVE_TARGET
|
||||
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/stove_target
|
||||
finding_behavior = /datum/ai_behavior/find_hunt_target/stove
|
||||
hunt_targets = list(/obj/machinery/oven/range)
|
||||
hunt_range = 9
|
||||
|
||||
/datum/ai_behavior/find_hunt_target/stove
|
||||
|
||||
/datum/ai_behavior/find_hunt_target/stove/valid_dinner(mob/living/source, obj/machinery/oven/range/stove, radius)
|
||||
if(!length(stove.used_tray?.contents) || stove.open)
|
||||
return FALSE
|
||||
//something in there is still baking...
|
||||
for(var/atom/baking in stove.used_tray)
|
||||
if(HAS_TRAIT(baking, TRAIT_BAKEABLE))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/ai_behavior/hunt_target/unarmed_attack_target/stove_target
|
||||
always_reset_target = TRUE
|
||||
|
||||
/datum/ai_behavior/hunt_target/unarmed_attack_target/stove_target/target_caught(mob/living/hunter, obj/machinery/oven/range/stove)
|
||||
if(stove.open)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/datum/ai_controller/basic_controller/cat/cake
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/decorate_donuts,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food,
|
||||
/datum/ai_planning_subtree/haul_food_to_young,
|
||||
/datum/ai_planning_subtree/random_speech/cats,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/decorate_donuts
|
||||
target_key = BB_DONUT_TARGET
|
||||
hunting_behavior = /datum/ai_behavior/hunt_target/decorate_donuts
|
||||
finding_behavior = /datum/ai_behavior/find_hunt_target/decorate_donuts
|
||||
hunt_targets = list(/obj/item/food/donut)
|
||||
hunt_range = 9
|
||||
|
||||
/datum/ai_behavior/find_hunt_target/decorate_donuts/valid_dinner(mob/living/source, obj/item/food/donut/target, radius)
|
||||
if(!target.is_decorated)
|
||||
return FALSE
|
||||
return can_see(source, target, radius)
|
||||
|
||||
/datum/ai_behavior/hunt_target/decorate_donuts
|
||||
always_reset_target = TRUE
|
||||
|
||||
/datum/ai_behavior/hunt_target/decorate_donuts/target_caught(mob/living/hunter, atom/target)
|
||||
hunter.spin(spintime = 4, speed = 1)
|
||||
@@ -0,0 +1,189 @@
|
||||
/mob/living/basic/pet/cat
|
||||
name = "cat"
|
||||
desc = "Kitty!!"
|
||||
icon = 'icons/mob/simple/pets.dmi'
|
||||
icon_state = "cat2"
|
||||
icon_living = "cat2"
|
||||
icon_dead = "cat2_dead"
|
||||
speak_emote = list("purrs", "meows")
|
||||
pass_flags = PASSTABLE
|
||||
mob_size = MOB_SIZE_SMALL
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
||||
unsuitable_atmos_damage = 0.5
|
||||
butcher_results = list(
|
||||
/obj/item/food/meat/slab = 1,
|
||||
/obj/item/organ/internal/ears/cat = 1,
|
||||
/obj/item/organ/external/tail/cat = 1,
|
||||
/obj/item/stack/sheet/animalhide/cat = 1
|
||||
)
|
||||
response_help_continuous = "pets"
|
||||
response_help_simple = "pet"
|
||||
response_disarm_continuous = "gently pushes aside"
|
||||
response_disarm_simple = "gently push aside"
|
||||
response_harm_continuous = "kicks"
|
||||
response_harm_simple = "kick"
|
||||
mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT
|
||||
gold_core_spawnable = FRIENDLY_SPAWN
|
||||
collar_icon_state = "cat"
|
||||
has_collar_resting_icon_state = TRUE
|
||||
can_be_held = TRUE
|
||||
ai_controller = /datum/ai_controller/basic_controller/cat
|
||||
held_state = "cat2"
|
||||
attack_verb_continuous = "claws"
|
||||
attack_verb_simple = "claw"
|
||||
attack_sound = 'sound/weapons/slash.ogg'
|
||||
attack_vis_effect = ATTACK_EFFECT_CLAW
|
||||
///can this cat breed?
|
||||
var/can_breed = TRUE
|
||||
///can hold items?
|
||||
var/can_hold_item = TRUE
|
||||
///can this cat interact with stoves?
|
||||
var/can_interact_with_stove = FALSE
|
||||
///list of items we can carry
|
||||
var/static/list/huntable_items = list(
|
||||
/obj/item/fish,
|
||||
/obj/item/food/deadmouse,
|
||||
/obj/item/food/fishmeat,
|
||||
)
|
||||
///item we are currently holding
|
||||
var/obj/item/held_food
|
||||
///mutable appearance for held item
|
||||
var/mutable_appearance/held_item_overlay
|
||||
|
||||
/mob/living/basic/pet/cat/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/ai_retaliate)
|
||||
AddElement(/datum/element/pet_bonus, "purrs!")
|
||||
add_verb(src, /mob/living/proc/toggle_resting)
|
||||
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
|
||||
ai_controller.set_blackboard_key(BB_HUNTABLE_PREY, typecacheof(huntable_items))
|
||||
if(can_breed)
|
||||
add_breeding_component()
|
||||
if(can_hold_item)
|
||||
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))
|
||||
if(can_interact_with_stove)
|
||||
RegisterSignal(src, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(pre_unarmed_attack))
|
||||
|
||||
/mob/living/basic/pet/cat/proc/pre_attack(mob/living/source, atom/movable/target)
|
||||
SIGNAL_HANDLER
|
||||
if(!is_type_in_list(target, huntable_items) || held_food)
|
||||
return
|
||||
target.forceMove(src)
|
||||
|
||||
/mob/living/basic/pet/cat/proc/pre_unarmed_attack(mob/living/hitter, atom/target, proximity, modifiers)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(istype(target, /obj/machinery/oven/range))
|
||||
target.attack_hand(src)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/mob/living/basic/pet/cat/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
if(gone != held_food)
|
||||
return
|
||||
held_food = null
|
||||
update_appearance(UPDATE_OVERLAYS)
|
||||
|
||||
/mob/living/basic/pet/cat/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
||||
if(is_type_in_list(arrived, huntable_items))
|
||||
held_food = arrived
|
||||
update_appearance(UPDATE_OVERLAYS)
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/pet/cat/update_overlays()
|
||||
. = ..()
|
||||
if(stat == DEAD || resting || !held_food)
|
||||
return
|
||||
if(istype(held_food, /obj/item/fish))
|
||||
held_item_overlay = mutable_appearance(icon, "cat_fish_overlay")
|
||||
if(istype(held_food, /obj/item/food/deadmouse))
|
||||
held_item_overlay = mutable_appearance(icon, "cat_mouse_overlay")
|
||||
. += held_item_overlay
|
||||
|
||||
/mob/living/basic/pet/cat/update_resting()
|
||||
. = ..()
|
||||
if(stat == DEAD)
|
||||
return
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
|
||||
/mob/living/basic/pet/cat/update_icon_state()
|
||||
. = ..()
|
||||
if (resting)
|
||||
icon_state = "[icon_living]_rest"
|
||||
return
|
||||
icon_state = "[icon_living]"
|
||||
|
||||
/mob/living/basic/pet/cat/proc/add_breeding_component()
|
||||
AddComponent(\
|
||||
/datum/component/breed,\
|
||||
can_breed_with = typecacheof(list(/mob/living/basic/pet/cat)),\
|
||||
baby_path = /mob/living/basic/pet/cat/kitten,\
|
||||
)
|
||||
|
||||
/mob/living/basic/pet/cat/space
|
||||
name = "space cat"
|
||||
desc = "They're a cat... in space!"
|
||||
icon_state = "spacecat"
|
||||
icon_living = "spacecat"
|
||||
icon_dead = "spacecat_dead"
|
||||
minimum_survivable_temperature = TCMB
|
||||
maximum_survivable_temperature = T0C + 40
|
||||
held_state = "spacecat"
|
||||
|
||||
/mob/living/basic/pet/cat/breadcat
|
||||
name = "bread cat"
|
||||
desc = "They're a cat... with a bread!"
|
||||
icon_state = "breadcat"
|
||||
icon_living = "breadcat"
|
||||
icon_dead = "breadcat_dead"
|
||||
ai_controller = /datum/ai_controller/basic_controller/cat/bread
|
||||
collar_icon_state = null
|
||||
held_state = "breadcat"
|
||||
can_interact_with_stove = TRUE
|
||||
butcher_results = list(
|
||||
/obj/item/food/meat/slab = 2,
|
||||
/obj/item/organ/internal/ears/cat = 1,
|
||||
/obj/item/organ/external/tail/cat = 1,
|
||||
/obj/item/food/breadslice/plain = 1
|
||||
)
|
||||
|
||||
|
||||
/mob/living/basic/pet/cat/original
|
||||
name = "Batsy"
|
||||
desc = "The product of alien DNA and bored geneticists."
|
||||
gender = FEMALE
|
||||
icon_state = "original"
|
||||
icon_living = "original"
|
||||
icon_dead = "original_dead"
|
||||
collar_icon_state = null
|
||||
unique_pet = TRUE
|
||||
held_state = "original"
|
||||
|
||||
/mob/living/basic/pet/cat/kitten
|
||||
name = "kitten"
|
||||
desc = "D'aaawwww."
|
||||
icon_state = "kitten"
|
||||
icon_living = "kitten"
|
||||
icon_dead = "kitten_dead"
|
||||
density = FALSE
|
||||
pass_flags = PASSMOB
|
||||
mob_size = MOB_SIZE_SMALL
|
||||
collar_icon_state = "kitten"
|
||||
can_breed = FALSE
|
||||
ai_controller = /datum/ai_controller/basic_controller/cat/kitten
|
||||
can_hold_item = FALSE
|
||||
|
||||
/mob/living/basic/pet/cat/kitten/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/basic_eating, food_types = huntable_items)
|
||||
|
||||
/mob/living/basic/pet/cat/_proc
|
||||
name = "Proc"
|
||||
gender = MALE
|
||||
gold_core_spawnable = NO_SPAWN
|
||||
unique_pet = TRUE
|
||||
|
||||
/mob/living/basic/pet/cat/jerry //Holy shit we left jerry on donut ~ Arcane ~Fikou
|
||||
name = "Jerry"
|
||||
desc = "Tom is VERY amused."
|
||||
gender = MALE
|
||||
@@ -0,0 +1,299 @@
|
||||
/datum/ai_controller/basic_controller/cat
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_HOSTILE_MEOWS = list("Mawwww", "Mrewwww", "mhhhhng..."),
|
||||
BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/cat/kitten),
|
||||
)
|
||||
|
||||
ai_movement = /datum/ai_movement/basic_avoidance
|
||||
idle_behavior = /datum/idle_behavior/idle_random_walk
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/reside_in_home,
|
||||
/datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/haul_food_to_young,
|
||||
/datum/ai_planning_subtree/territorial_struggle,
|
||||
/datum/ai_planning_subtree/make_babies,
|
||||
/datum/ai_planning_subtree/random_speech/cats,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/reside_in_home
|
||||
///chance we enter our home
|
||||
var/reside_chance = 5
|
||||
///chance we leave our home
|
||||
var/leave_home_chance = 15
|
||||
|
||||
/datum/ai_planning_subtree/reside_in_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
|
||||
if(controller.blackboard_key_exists(BB_CAT_HOME))
|
||||
controller.queue_behavior(/datum/ai_behavior/enter_cat_home, BB_CAT_HOME)
|
||||
return
|
||||
|
||||
if(istype(living_pawn.loc, /obj/structure/cat_house))
|
||||
if(SPT_PROB(leave_home_chance, seconds_per_tick))
|
||||
controller.set_blackboard_key(BB_CAT_HOME, living_pawn.loc)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
if(SPT_PROB(reside_chance, seconds_per_tick))
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_home, BB_CAT_HOME, /obj/structure/cat_house)
|
||||
|
||||
/datum/ai_behavior/find_and_set/valid_home/search_tactic(datum/ai_controller/controller, locate_path, search_range)
|
||||
for(var/obj/structure/cat_house/home in oview(search_range, controller.pawn))
|
||||
if(home.resident_cat)
|
||||
continue
|
||||
return home
|
||||
|
||||
return null
|
||||
|
||||
/datum/ai_behavior/enter_cat_home
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
||||
|
||||
/datum/ai_behavior/enter_cat_home/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/enter_cat_home/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/obj/structure/cat_house/home = controller.blackboard[target_key]
|
||||
var/mob/living/basic/living_pawn = controller.pawn
|
||||
if(living_pawn == home.resident_cat || isnull(home.resident_cat))
|
||||
living_pawn.melee_attack(home)
|
||||
finish_action(controller, TRUE, target_key)
|
||||
return
|
||||
|
||||
finish_action(controller, FALSE, target_key)
|
||||
|
||||
/datum/ai_behavior/enter_cat_home/finish_action(datum/ai_controller/controller, success, target_key)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
|
||||
/datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle
|
||||
flee_behaviour = /datum/ai_behavior/run_away_from_target/cat_struggle
|
||||
|
||||
/datum/ai_behavior/run_away_from_target/cat_struggle
|
||||
clear_failed_targets = TRUE
|
||||
|
||||
/datum/ai_planning_subtree/territorial_struggle
|
||||
///chance we become hostile to another cat
|
||||
var/hostility_chance = 5
|
||||
|
||||
/datum/ai_planning_subtree/territorial_struggle/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
if(living_pawn.gender != MALE || !SPT_PROB(hostility_chance, seconds_per_tick))
|
||||
return
|
||||
if(controller.blackboard_key_exists(BB_TRESSPASSER_TARGET))
|
||||
controller.queue_behavior(/datum/ai_behavior/territorial_struggle, BB_TRESSPASSER_TARGET, BB_HOSTILE_MEOWS)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/cat_tresspasser, BB_TRESSPASSER_TARGET, /mob/living/basic/pet/cat)
|
||||
|
||||
/datum/ai_behavior/find_and_set/cat_tresspasser/search_tactic(datum/ai_controller/controller, locate_path, search_range)
|
||||
var/list/ignore_types = controller.blackboard[BB_BABIES_CHILD_TYPES]
|
||||
for(var/mob/living/basic/pet/cat/potential_enemy in oview(search_range, controller.pawn))
|
||||
if(potential_enemy.gender != MALE)
|
||||
continue
|
||||
if(is_type_in_list(potential_enemy, ignore_types))
|
||||
continue
|
||||
var/datum/ai_controller/basic_controller/enemy_controller = potential_enemy.ai_controller
|
||||
if(isnull(enemy_controller))
|
||||
continue
|
||||
//theyre already engaged in a battle, leave them alone!
|
||||
if(enemy_controller.blackboard_key_exists(BB_TRESSPASSER_TARGET))
|
||||
continue
|
||||
//u choose me and i choose u
|
||||
enemy_controller.set_blackboard_key(BB_TRESSPASSER_TARGET, controller.pawn)
|
||||
return potential_enemy
|
||||
return null
|
||||
|
||||
/datum/ai_behavior/territorial_struggle
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
||||
action_cooldown = 5 SECONDS
|
||||
///chance the battle ends!
|
||||
var/end_battle_chance = 25
|
||||
|
||||
/datum/ai_behavior/territorial_struggle/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
if(target.ai_controller?.blackboard[target_key] != living_pawn)
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/territorial_struggle/perform(seconds_per_tick, datum/ai_controller/controller, target_key, cries_key)
|
||||
. = ..()
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
|
||||
if(QDELETED(target))
|
||||
finish_action(controller, TRUE, target_key)
|
||||
return
|
||||
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/list/threaten_list = controller.blackboard[cries_key]
|
||||
if(length(threaten_list))
|
||||
living_pawn.say(pick(threaten_list), forced = "ai_controller")
|
||||
|
||||
if(!prob(end_battle_chance))
|
||||
return
|
||||
|
||||
//50 50 chance we lose
|
||||
var/datum/ai_controller/loser_controller = prob(50) ? controller : target.ai_controller
|
||||
|
||||
loser_controller.set_blackboard_key(BB_BASIC_MOB_FLEE_TARGET, target)
|
||||
target.ai_controller.clear_blackboard_key(BB_TRESSPASSER_TARGET)
|
||||
finish_action(controller, TRUE, target_key)
|
||||
|
||||
/datum/ai_behavior/territorial_struggle/finish_action(datum/ai_controller/controller, success, target_key)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice
|
||||
target_key = BB_MOUSE_TARGET
|
||||
hunting_behavior = /datum/ai_behavior/play_with_mouse
|
||||
finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_mice
|
||||
hunt_targets = list(/mob/living/basic/mouse)
|
||||
hunt_chance = 75
|
||||
hunt_range = 9
|
||||
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/list/items_we_carry = typecache_filter_list(living_pawn, controller.blackboard[BB_HUNTABLE_PREY])
|
||||
if(length(items_we_carry))
|
||||
return
|
||||
return ..()
|
||||
|
||||
|
||||
/datum/ai_behavior/find_hunt_target/hunt_mice/valid_dinner(mob/living/source, mob/living/mouse, radius)
|
||||
if(mouse.stat == DEAD || mouse.mind)
|
||||
return FALSE
|
||||
return can_see(source, mouse, radius)
|
||||
|
||||
//play as in kill
|
||||
/datum/ai_behavior/play_with_mouse
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
||||
action_cooldown = 10 SECONDS
|
||||
///chance we hunt the mouse!
|
||||
var/consume_chance = 70
|
||||
|
||||
/datum/ai_behavior/play_with_mouse/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/play_with_mouse/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/mob/living/basic/mouse/target = controller.blackboard[target_key]
|
||||
|
||||
if(QDELETED(target))
|
||||
finish_action(controller, TRUE, target_key)
|
||||
return
|
||||
|
||||
consume_chance = istype(target, /mob/living/basic/mouse/brown/tom) ? 5 : initial(consume_chance)
|
||||
if(prob(consume_chance))
|
||||
target.splat()
|
||||
finish_action(controller, TRUE, target_key)
|
||||
return
|
||||
finish_action(controller, FALSE, target_key)
|
||||
|
||||
/datum/ai_behavior/play_with_mouse/finish_action(datum/ai_controller/controller, success, target_key)
|
||||
. = ..()
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
controller.clear_blackboard_key(target_key)
|
||||
if(isnull(target))
|
||||
return
|
||||
var/manual_emote = "attempts to hunt [target]..."
|
||||
var/end_result = success ? "and succeeds!" : "but fails!"
|
||||
manual_emote += end_result
|
||||
living_pawn.manual_emote(manual_emote)
|
||||
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food
|
||||
target_key = BB_CAT_FOOD_TARGET
|
||||
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/find_cat_food
|
||||
finding_behavior = /datum/ai_behavior/find_hunt_target/find_cat_food
|
||||
hunt_targets = list(/obj/item/fish, /obj/item/food/deadmouse, /obj/item/food/fishmeat)
|
||||
hunt_chance = 75
|
||||
hunt_range = 9
|
||||
|
||||
/datum/ai_behavior/hunt_target/unarmed_attack_target/find_cat_food
|
||||
always_reset_target = TRUE
|
||||
|
||||
/datum/ai_behavior/find_hunt_target/find_cat_food/valid_dinner(mob/living/source, atom/dinner, radius)
|
||||
//this food is already near a kitten, let the kitten eat it
|
||||
var/mob/living/nearby_kitten = locate(/mob/living/basic/pet/cat/kitten) in oview(2, dinner)
|
||||
if(nearby_kitten && nearby_kitten != source)
|
||||
return FALSE
|
||||
return can_see(source, dinner, radius)
|
||||
|
||||
/datum/ai_planning_subtree/haul_food_to_young/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if(!controller.blackboard_key_exists(BB_FOOD_TO_DELIVER))
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands/given_list, BB_FOOD_TO_DELIVER, controller.blackboard[BB_HUNTABLE_PREY])
|
||||
return
|
||||
if(!controller.blackboard_key_exists(BB_KITTEN_TO_FEED))
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_kitten, BB_KITTEN_TO_FEED, /mob/living/basic/pet/cat/kitten)
|
||||
return
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/deliver_food_to_kitten, BB_KITTEN_TO_FEED, BB_FOOD_TO_DELIVER)
|
||||
|
||||
/datum/ai_behavior/find_and_set/valid_kitten
|
||||
|
||||
/datum/ai_behavior/find_and_set/valid_kitten/search_tactic(datum/ai_controller/controller, locate_path, search_range)
|
||||
var/mob/living/kitten = locate(locate_path) in oview(search_range, controller.pawn)
|
||||
//kitten already has food near it, go feed another hungry kitten
|
||||
|
||||
if(isnull(kitten))
|
||||
return null
|
||||
|
||||
var/list/nearby_food = typecache_filter_list(oview(2, kitten), controller.blackboard[BB_HUNTABLE_PREY])
|
||||
if(kitten.stat != DEAD && !length(nearby_food))
|
||||
return kitten
|
||||
return null
|
||||
|
||||
/datum/ai_behavior/deliver_food_to_kitten
|
||||
/datum/ai_behavior/deliver_food_to_kitten
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
||||
action_cooldown = 5 SECONDS
|
||||
|
||||
/datum/ai_behavior/deliver_food_to_kitten/setup(datum/ai_controller/controller, target_key, food_key)
|
||||
. = ..()
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/deliver_food_to_kitten/perform(seconds_per_tick, datum/ai_controller/controller, target_key, food_key)
|
||||
. = ..()
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
|
||||
if(QDELETED(target))
|
||||
finish_action(controller, FALSE, target_key, food_key)
|
||||
return
|
||||
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/atom/movable/food = controller.blackboard[food_key]
|
||||
|
||||
if(isnull(food) || !(food in living_pawn))
|
||||
finish_action(controller, FALSE, target_key, food_key)
|
||||
return
|
||||
|
||||
food.forceMove(get_turf(living_pawn))
|
||||
finish_action(controller, TRUE, target_key, food_key)
|
||||
|
||||
/datum/ai_behavior/deliver_food_to_kitten/finish_action(datum/ai_controller/controller, success, target_key, food_key)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(target_key)
|
||||
controller.clear_blackboard_key(food_key)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/mob/living/basic/pet/cat/cak
|
||||
name = "Keeki"
|
||||
desc = "She is a cat made out of cake."
|
||||
icon_state = "cak"
|
||||
icon_living = "cak"
|
||||
icon_dead = "cak_dead"
|
||||
health = 50
|
||||
maxHealth = 50
|
||||
gender = FEMALE
|
||||
butcher_results = list(
|
||||
/obj/item/organ/internal/brain = 1,
|
||||
/obj/item/organ/internal/heart = 1,
|
||||
/obj/item/food/cakeslice/birthday = 3,
|
||||
/obj/item/food/meat/slab = 2
|
||||
)
|
||||
response_harm_continuous = "takes a bite out of"
|
||||
response_harm_simple = "take a bite out of"
|
||||
ai_controller = /datum/ai_controller/basic_controller/cat/cake
|
||||
attacked_sound = 'sound/items/eatfood.ogg'
|
||||
death_message = "loses her false life and collapses!"
|
||||
death_sound = SFX_BODYFALL
|
||||
held_state = "cak"
|
||||
can_interact_with_stove = TRUE
|
||||
|
||||
/mob/living/basic/pet/cat/cak/Initialize(mapload)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/regenerator,\
|
||||
regeneration_delay = 1 SECONDS,\
|
||||
brute_per_second = 5,\
|
||||
outline_colour = COLOR_PINK,\
|
||||
)
|
||||
var/static/list/on_consume = list(
|
||||
/datum/reagent/consumable/nutriment = 0.4,
|
||||
/datum/reagent/consumable/nutriment/vitamin = 0.4,
|
||||
)
|
||||
AddElement(/datum/element/consumable_mob, reagents_list = on_consume)
|
||||
|
||||
/mob/living/basic/pet/cat/cak/CheckParts(list/parts)
|
||||
. = ..()
|
||||
var/obj/item/organ/internal/brain/candidate = locate(/obj/item/organ/internal/brain) in contents
|
||||
if(isnull(candidate?.brainmob?.mind))
|
||||
return
|
||||
var/datum/mind/candidate_mind = candidate.brainmob.mind
|
||||
candidate_mind.transfer_to(src)
|
||||
candidate_mind.grab_ghost()
|
||||
to_chat(src, "[span_boldbig("You are a cak!")]<b> You're a harmless cat/cake hybrid that everyone loves. People can take bites out of you if they're hungry, but you regenerate health \
|
||||
so quickly that it generally doesn't matter. You're remarkably resilient to any damage besides this and it's hard for you to really die at all. You should go around and bring happiness and \
|
||||
free cake to the station!</b>")
|
||||
var/default_name = initial(name)
|
||||
var/new_name = sanitize_name(reject_bad_text(tgui_input_text(src, "You are the [name]. Would you like to change your name to something else?", "Name change", default_name, MAX_NAME_LEN)), cap_after_symbols = FALSE)
|
||||
if(new_name)
|
||||
to_chat(src, span_notice("Your name is now <b>[new_name]</b>!"))
|
||||
name = new_name
|
||||
|
||||
/mob/living/basic/pet/cat/cak/spin(spintime, speed)
|
||||
. = ..()
|
||||
for(var/obj/item/food/donut/target in oview(1, src))
|
||||
if(!target.is_decorated)
|
||||
target.decorate_donut()
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
/datum/ai_controller/basic_controller/cat/kitten
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_HUNGRY_MEOW = list("mrrp...", "mraw..."),
|
||||
BB_MAX_DISTANCE_TO_FOOD = 2,
|
||||
)
|
||||
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/target_retaliate,
|
||||
/datum/ai_planning_subtree/flee_target,
|
||||
/datum/ai_planning_subtree/beg_human,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten,
|
||||
/datum/ai_planning_subtree/random_speech/cats,
|
||||
)
|
||||
|
||||
//if the food is too far away, point at it or meow. if its near us then go eat it
|
||||
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten
|
||||
|
||||
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/atom/target = controller.blackboard[BB_CAT_FOOD_TARGET]
|
||||
if(target && get_dist(target, controller.pawn) > controller.blackboard[BB_MAX_DISTANCE_TO_FOOD])
|
||||
controller.queue_behavior(/datum/ai_behavior/beacon_for_food, BB_CAT_FOOD_TARGET, BB_HUNGRY_MEOW)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/datum/ai_behavior/beacon_for_food
|
||||
action_cooldown = 5 SECONDS
|
||||
|
||||
/datum/ai_behavior/beacon_for_food/perform(seconds_per_tick, datum/ai_controller/controller, target_key, meows_key)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
finish_action(controller, FALSE)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/list/meowing_list = controller.blackboard[meows_key]
|
||||
if(length(meowing_list))
|
||||
living_pawn.say(pick(meowing_list), forced = "ai_controller")
|
||||
living_pawn._pointed(target)
|
||||
finish_action(controller, TRUE)
|
||||
|
||||
/datum/ai_behavior/beacon_for_food/finish_action(datum/ai_controller/controller, success, target_key)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
/datum/ai_planning_subtree/beg_human
|
||||
|
||||
/datum/ai_planning_subtree/beg_human/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
|
||||
if(controller.blackboard_key_exists(BB_HUMAN_BEG_TARGET))
|
||||
controller.queue_behavior(/datum/ai_behavior/beacon_for_food, BB_HUMAN_BEG_TARGET, BB_HUNGRY_MEOW)
|
||||
return
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/human_beg, BB_HUMAN_BEG_TARGET, /mob/living/carbon/human)
|
||||
|
||||
/datum/ai_behavior/find_and_set/human_beg/search_tactic(datum/ai_controller/controller, locate_path, search_range)
|
||||
var/list/locate_items = controller.blackboard[BB_HUNTABLE_PREY]
|
||||
for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn))
|
||||
if(human_target.stat != CONSCIOUS || isnull(human_target.mind))
|
||||
continue
|
||||
if(!length(typecache_filter_list(human_target.held_items, locate_items)))
|
||||
continue
|
||||
return human_target
|
||||
|
||||
return null
|
||||
@@ -0,0 +1,99 @@
|
||||
#define RUNTIME_SAVE_DATA "data/npc_saves/Runtime.sav"
|
||||
#define RUNTIME_JSON_DATA "data/npc_saves/Runtime.json"
|
||||
#define MAX_CAT_DEPLOY 50
|
||||
|
||||
/mob/living/basic/pet/cat/runtime
|
||||
name = "Runtime"
|
||||
desc = "GCAT"
|
||||
icon_state = "cat"
|
||||
icon_living = "cat"
|
||||
icon_dead = "cat_dead"
|
||||
gender = FEMALE
|
||||
gold_core_spawnable = NO_SPAWN
|
||||
unique_pet = TRUE
|
||||
///the family we will bring in when a round starts
|
||||
var/list/family = null
|
||||
///saved list of kids
|
||||
var/list/children = null
|
||||
/// have we deployed the cats?
|
||||
var/cats_deployed = FALSE
|
||||
/// have we saved memory?
|
||||
var/memory_saved = FALSE
|
||||
///callback we use to register our family
|
||||
var/datum/callback/register_family
|
||||
|
||||
/mob/living/basic/pet/cat/runtime/Initialize(mapload)
|
||||
. = ..()
|
||||
register_family = CALLBACK(src, PROC_REF(Write_Memory))
|
||||
SSticker.OnRoundend(register_family)
|
||||
if(mapload)
|
||||
read_memory()
|
||||
deploy_the_cats()
|
||||
|
||||
if(!prob(5))
|
||||
return
|
||||
icon_state = "original"
|
||||
icon_living = "original"
|
||||
icon_dead = "original_dead"
|
||||
update_appearance()
|
||||
|
||||
|
||||
/mob/living/basic/pet/cat/runtime/add_breeding_component()
|
||||
AddComponent(\
|
||||
/datum/component/breed,\
|
||||
can_breed_with = typecacheof(list(/mob/living/basic/pet/cat)),\
|
||||
baby_path = /mob/living/basic/pet/cat/kitten,\
|
||||
post_birth = CALLBACK(src, PROC_REF(after_birth)),\
|
||||
)
|
||||
|
||||
/mob/living/basic/pet/cat/runtime/proc/after_birth(mob/living/baby)
|
||||
if(isnull(baby))
|
||||
return
|
||||
LAZYADD(children, baby)
|
||||
|
||||
/mob/living/basic/pet/cat/runtime/proc/read_memory()
|
||||
if(fexists(RUNTIME_SAVE_DATA))
|
||||
var/savefile/save_data = new(RUNTIME_SAVE_DATA)
|
||||
save_data["family"] >> family
|
||||
fdel(RUNTIME_SAVE_DATA)
|
||||
return
|
||||
var/json_file = file(RUNTIME_JSON_DATA)
|
||||
if(!fexists(json_file))
|
||||
return
|
||||
var/list/json_list = json_decode(file2text(json_file))
|
||||
family = json_list["family"]
|
||||
|
||||
/mob/living/basic/pet/cat/runtime/Destroy()
|
||||
LAZYREMOVE(SSticker.round_end_events, register_family)
|
||||
register_family = null
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/pet/cat/runtime/Write_Memory(dead, gibbed)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
var/json_file = file(RUNTIME_JSON_DATA)
|
||||
var/list/file_data = list()
|
||||
if(!dead)
|
||||
for(var/mob/living/basic/pet/cat/kitten/kitten in children)
|
||||
if(kitten.stat == DEAD)
|
||||
continue
|
||||
if(kitten.type in family)
|
||||
family[kitten.type] += 1
|
||||
continue
|
||||
family[kitten.type] = 1
|
||||
file_data["family"] = family
|
||||
fdel(json_file)
|
||||
WRITE_FILE(json_file, json_encode(file_data, JSON_PRETTY_PRINT))
|
||||
|
||||
/mob/living/basic/pet/cat/runtime/proc/deploy_the_cats()
|
||||
cats_deployed = TRUE
|
||||
for(var/cat_type in family)
|
||||
if(isnull(family[cat_type]))
|
||||
return
|
||||
for(var/index in 1 to min(family[cat_type], MAX_CAT_DEPLOY))
|
||||
new cat_type(loc)
|
||||
|
||||
#undef RUNTIME_SAVE_DATA
|
||||
#undef RUNTIME_JSON_DATA
|
||||
#undef MAX_CAT_DEPLOY
|
||||
Reference in New Issue
Block a user