Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions code/modules/mob/living/simple_animal/crow/crow.dm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
storage = /datum/storage/backpack/crow
material = /decl/material/solid/organic/cloth

// TODO: Merge with /mob/living/simple_animal/passive/bird/crow
/mob/living/simple_animal/crow
name = "crow"
desc = "A large crow. Caw caw."
Expand All @@ -15,7 +16,7 @@
mob_size = MOB_SIZE_SMALL
speak_emote = list("caws")
ai = /datum/mob_controller/crow
natural_weapon = /obj/item/natural_weapon/crow_claws
natural_weapon = /obj/item/natural_weapon/bird_claws
universal_speak = TRUE

/datum/mob_controller/crow
Expand All @@ -34,7 +35,7 @@
/mob/living/simple_animal/crow/get_bodytype()
return GET_DECL(/decl/bodytype/animal/crow)

/obj/item/natural_weapon/crow_claws
/obj/item/natural_weapon/bird_claws
name = "claws"
gender = PLURAL
attack_verb = "clawed"
Expand Down
13 changes: 11 additions & 2 deletions code/modules/mob_holder/_holder.dm
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,18 @@
if(length(cards))
LAZYDISTINCTADD(., cards)

/obj/item/holder/handle_mouse_drop(atom/over, mob/user, params)
if(over == user && user != src && !(user in src))
for(var/mob/M in contents)
M.show_stripping_window(user) // TODO: verify that you can even strip items from a mob in an item
. = TRUE
. = . || ..()

/obj/item/holder/attack_self(mob/user)
for(var/mob/M in contents)
M.show_stripping_window(user)
var/mob/living/bird = locate() in contents
if(istype(bird?.ai))
bird.ai.process_holder_interaction(user)
return TRUE

/obj/item/holder/use_on_mob(mob/living/target, mob/living/user, animate = TRUE)

Expand Down
2 changes: 2 additions & 0 deletions mods/content/birds/_birds.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/decl/modpack/birds
name = "Birds"
11 changes: 11 additions & 0 deletions mods/content/birds/_birds.dme
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef MODPACK_BIRDS
#define MODPACK_BIRDS
// BEGIN_INCLUDE
#include "_birds.dm"
#include "bird.dm"
#include "bird_crow.dm"
#include "bird_hawk.dm"
#include "bird_pigeon.dm"
#include "hutch.dm"
// END_INCLUDE
#endif
25 changes: 25 additions & 0 deletions mods/content/birds/bird.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/mob/living/simple_animal/passive/bird
mob_size = MOB_SIZE_SMALL
pass_flags = PASS_FLAG_TABLE
abstract_type = /mob/living/simple_animal/passive/bird
natural_weapon = /obj/item/natural_weapon/bird_claws
holder_type = /obj/item/holder/bird

/obj/item/holder/bird
w_class = MOB_SIZE_SMALL

/obj/item/holder/bird/afterattack(atom/target, mob/user, proximity)
if(proximity)
return ..()
var/mob/living/bird = locate() in contents
. = ..()
if(!user || !bird || QDELETED(src) || bird.loc != src)
return
bird.dropInto(loc)
qdel(src) // This will happen shortly regardless, but might as well skip the 1ds delay.
if(isturf(target))
bird.visible_message(SPAN_NOTICE("\The [user] releases \a [bird]!"))
else
bird.visible_message(SPAN_NOTICE("\The [user] indicates \the [target] and releases \a [bird]!"))
if(istype(bird.ai))
bird.ai.process_handler_target(user, target, user.get_intent()?.intent_flags)
10 changes: 10 additions & 0 deletions mods/content/birds/bird_crow.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/datum/mob_controller/passive/crow
emote_speech = list("Caw.","Caw!","Caw...")
emote_hear = list("croaks", "caws")
emote_see = list("preens its feathers", "hops around")

/mob/living/simple_animal/passive/bird/crow
name = "crow"
icon = 'mods/content/birds/icons/crow.dmi'
ai = /datum/mob_controller/passive/crow
ability_handlers = list(/datum/ability_handler/predator) // should really be /scavenger
82 changes: 82 additions & 0 deletions mods/content/birds/bird_hawk.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/mob/living/simple_animal/passive/bird/hawk
name = "hawk"
icon = 'mods/content/birds/icons/hawk.dmi'
ai = /datum/mob_controller/passive/hunter/hawk
ability_handlers = list(/datum/ability_handler/predator)

/datum/mob_controller/passive/hunter/hawk
emote_speech = list("Skree!","SKREE!","Skree!?")
emote_hear = list("screeches", "screams")
emote_see = list("preens its feathers", "flicks its wings", "looks sharply around")
var/handler_set_target = FALSE
var/handling_skill = SKILL_BOTANY
var/handling_difficulty = SKILL_ADEPT

/datum/mob_controller/passive/hunter/hawk/consume_prey(mob/living/prey)
if(prey.stat == DEAD && last_handler && handler_set_target)
set_target(last_handler?.resolve())
prey.try_make_grab(body, defer_hand = TRUE)
return
return ..()

/datum/mob_controller/passive/hunter/hawk/set_target(atom/new_target)
. = ..()
handler_set_target = FALSE

/datum/mob_controller/passive/hunter/hawk/process_handler_target(mob/handler, atom/target)
if((. = ..()))
set_target(target)
handler_set_target = TRUE
process_hunting(target)

/datum/mob_controller/passive/hunter/hawk/can_hunt(mob/living/victim)
return handler_set_target || ..()

/datum/mob_controller/passive/hunter/hawk/check_handler_can_order(mob/handler, atom/target, intent_flags)
if(!(. = ..()) && handler.skill_check(handling_skill, handling_difficulty))
add_friend(handler)
return ..()

/datum/mob_controller/passive/hunter/hawk/process_handler_failure(mob/handler, atom/target)
body?.visible_message(SPAN_DANGER("\The [body] ignores \the [target] in favour of attacking \the [handler]!"))
set_target(handler)
handler_set_target = TRUE
next_hunt = 0
return ..()

/datum/mob_controller/passive/hunter/hawk/handle_friend_hunting(mob/user)
..()
set_target(null)
resume_wandering()
if(!body)
return
if(body.scoop_check(user) && body.get_scooped(user, body, silent = TRUE))
body.visible_message(SPAN_NOTICE("\The [body] alights on \the [user]."))
else
body.visible_message(SPAN_NOTICE("\The [body] lands beside \the [user]."))

for(var/obj/item/thing in body.get_equipped_items(include_carried = TRUE))
body.drop_from_inventory(thing)
if(!QDELETED(thing))
user.put_in_hands(thing)
var/equipped_to = user.get_equipped_slot_for_item(thing)
var/datum/inventory_slot/slot = equipped_to && user.get_inventory_slot_datum(equipped_to)
if(istype(slot))
to_chat(user, SPAN_NOTICE("\The [body] drops \a [thing] into your [lowertext(slot.slot_name)]."))
else
to_chat(user, SPAN_NOTICE("\The [body] drops \a [thing]."))

return TRUE

/datum/mob_controller/passive/hunter/hawk/process_hunting(atom/target)
// Handles pathing to the target, and attacking the target if it's a mob.
if(!(. = ..()))
return
// Maybe consider handling structures at some point?
if(isitem(target) && body.Adjacent(target))
body.put_in_hands(target)
if(target.loc != body)
body.visible_message(SPAN_WARNING("\The [body] fails to collect \the [target]!"))
// Return to handler.
set_target(last_handler?.resolve())
return FALSE
67 changes: 67 additions & 0 deletions mods/content/birds/bird_pigeon.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/mob/living/simple_animal/passive/bird/pigeon
name = "messenger pigeon"
icon = 'mods/content/birds/icons/pigeon.dmi'
ai = /datum/mob_controller/passive/pigeon
holder_type = /obj/item/holder/bird/pigeon
var/weakref/home_hutch

/mob/living/simple_animal/passive/bird/pigeon/Initialize()
. = ..()
update_hutch()

/mob/living/simple_animal/passive/bird/pigeon/proc/go_home(mob/releaser)
if(!is_outside())
return
var/obj/structure/hutch/hutch = home_hutch?.resolve()
if(!istype(hutch) || QDELETED(hutch))
return // todo: check if the hutch is accessible from the sky
if(releaser)
releaser.visible_message(SPAN_NOTICE("\The [releaser] releases \a [src], which flutters away into the sky."))
else
visible_message(SPAN_NOTICE("\The [src] flutters away into the sky."))
set_dir(SOUTH)
// this is done manually due to the actual flying state primarily being handled as a movement state.
icon_state = "world-flying"
new /obj/effect/dummy/fadeout(loc, NORTH, src)
new /obj/effect/dummy/fadein(get_turf(hutch), SOUTH, src)
update_icon()

hutch.visible_message(SPAN_NOTICE("\A [src] alights on \the [hutch] in a flutter of wings."))
var/obj/item/holder/bird_item = new holder_type
forceMove(bird_item)
bird_item.sync(src)
hutch.storage?.handle_item_insertion(null, bird_item)
if(bird_item.loc != hutch)
dropInto(hutch.loc)
qdel(bird_item)

/obj/item/holder/bird/pigeon/attack_self(mob/user)
var/mob/living/simple_animal/passive/bird/pigeon/pigeon = locate() in contents
if(!istype(pigeon))
return ..()
if(!is_outside())
to_chat(user, SPAN_WARNING("You need to be outdoors to release \the [pigeon]."))
return TRUE
if(isnull(pigeon.home_hutch))
var/decl/pronouns/pronouns = pigeon.get_pronouns()
to_chat(user, SPAN_WARNING("\The [pigeon] tilts [pronouns.his] head at you in confusion. [pronouns.He] must not have a hutch to return to."))
else
user.drop_from_inventory(src)
pigeon.go_home(user)
qdel(src)
return TRUE

/mob/living/simple_animal/passive/bird/pigeon/proc/update_hutch()
var/obj/structure/hutch/hutch = home_hutch?.resolve()
if(!istype(hutch) || QDELETED(hutch))
hutch = get_recursive_loc_of_type(/obj/structure/hutch)
if(istype(hutch) && !QDELETED(hutch))
home_hutch = weakref(hutch)
events_repository.unregister(/decl/observ/moved, src, src)
else
events_repository.register(/decl/observ/moved, src, src, TYPE_PROC_REF(/mob/living/simple_animal/passive/bird/pigeon, update_hutch))

/datum/mob_controller/passive/pigeon
emote_speech = list("Oo-ooo.","Oo-ooo?","Oo-ooo...")
emote_hear = list("coos")
emote_see = list("preens its feathers", "puffs out its neck", "ruffles its wings")
71 changes: 71 additions & 0 deletions mods/content/birds/hutch.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/datum/storage/hutch
can_hold = list(/obj/item/holder)
max_w_class = MOB_SIZE_SMALL
storage_slots = 10

/datum/storage/hutch/open(mob/user)
. = ..()
var/atom/hutch = holder
if(istype(hutch))
hutch.queue_icon_update()

/datum/storage/hutch/close(mob/user)
. = ..()
var/atom/hutch = holder
if(istype(hutch))
hutch.queue_icon_update()

/obj/structure/hutch
name = "hutch"
icon = 'mods/content/birds/icons/hutch.dmi'
desc = "A hutch for containing small animals like rabbits."
icon_state = ICON_STATE_WORLD
material = /decl/material/solid/organic/wood/oak
material_alteration = MAT_FLAG_ALTERATION_ALL
storage = /datum/storage/hutch
var/initial_animal_count = 5
var/decl/material/door_material = /decl/material/solid/organic/plantmatter/grass/dry
var/initial_animal_type

/obj/structure/hutch/Initialize(ml, _mat, _reinf_mat)
. = ..()

if(ispath(door_material))
door_material = GET_DECL(door_material)

if(initial_animal_type && initial_animal_count)
for(var/i = 1 to initial_animal_count)
var/mob/bird_type = islist(initial_animal_type) ? pick(initial_animal_type) : initial_animal_type
var/bird_holder_type = bird_type::holder_type
var/obj/item/holder/bird/bird_item = new bird_holder_type(src)
bird_item.sync(new bird_type(bird_item))
else
update_icon()

/obj/structure/hutch/on_update_icon()
. = ..()
if(door_material)
add_overlay(overlay_image(icon, "[icon_state]-doors-[storage?.opened ? "open" : "closed"]", door_material.color, RESET_COLOR))

// Bird subtypes.
/obj/structure/hutch/aviary
name = "aviary"
desc = "A hutch for containing birds like hawks or crows."
icon = 'mods/content/birds/icons/aviary.dmi'

/obj/structure/hutch/aviary/crow
initial_animal_type = /mob/living/simple_animal/passive/bird/crow

/obj/structure/hutch/aviary/pigeon
initial_animal_type = /mob/living/simple_animal/passive/bird/pigeon

/obj/structure/hutch/aviary/hawk
initial_animal_type = /mob/living/simple_animal/passive/bird/hawk

// Rabbits are a kind of bird, right?
/obj/structure/hutch/rabbit
initial_animal_type = list(
/mob/living/simple_animal/passive/rabbit,
/mob/living/simple_animal/passive/rabbit/black,
/mob/living/simple_animal/passive/rabbit/brown
)
Binary file added mods/content/birds/icons/aviary.dmi
Binary file not shown.
Binary file added mods/content/birds/icons/crow.dmi
Binary file not shown.
Binary file added mods/content/birds/icons/hawk.dmi
Binary file not shown.
Binary file added mods/content/birds/icons/hutch.dmi
Binary file not shown.
Binary file added mods/content/birds/icons/pigeon.dmi
Binary file not shown.
7 changes: 5 additions & 2 deletions mods/content/fantasy/datum/skills.dm
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,11 @@
"Master" = "You're a specialized animal caretaker. You can care for even the most exotic, fragile, or dangerous animals."
)

/obj/item/food/egg/examine_skill = SKILL_HUSBANDRY
/mob/living/simple_animal/chick/examine_skill = SKILL_HUSBANDRY
/obj/item/food/egg
examine_skill = SKILL_HUSBANDRY

/mob/living/simple_animal/chick
examine_skill = SKILL_HUSBANDRY

/datum/extension/milkable
milking_skill = SKILL_HUSBANDRY
Expand Down
7 changes: 6 additions & 1 deletion mods/~compatibility/patches/fantasy.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@

#ifdef MODPACK_BLACKSMITHY
#include "fantasy/forging_fantasy.dm"
#endif
#endif

// Override hawk handling skill.
#ifdef MODPACK_BIRDS
#include "fantasy/bird_fantasy.dm"
#endif
2 changes: 2 additions & 0 deletions mods/~compatibility/patches/fantasy/bird_fantasy.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/datum/mob_controller/passive/hunter/hawk
handling_skill = SKILL_HUSBANDRY
Loading