diff --git a/code/game/objects/structures/animal_den.dm b/code/game/objects/structures/animal_den.dm new file mode 100644 index 0000000000..33f50297a0 --- /dev/null +++ b/code/game/objects/structures/animal_den.dm @@ -0,0 +1,161 @@ +/obj/structure/animal_den + name = "den" + icon = 'icons/obj/structures/den.dmi' + icon_state = "den1" + desc = "A rough-walled, shallow den dug into the earth." + density = FALSE + opacity = FALSE + +/obj/structure/animal_den/Initialize() + icon_state = "den[rand(1,3)]" + . = ..() + +/obj/structure/animal_den/proc/remove_from_den(var/atom/movable/AM) + AM.dropInto(get_turf(src)) + if(ismob(AM)) + var/mob/M = AM + if(M.client) + M.client.eye = M.client.mob + M.client.perspective = MOB_PERSPECTIVE + update_icon() + +/obj/structure/animal_den/relaymove(mob/user, direction) + remove_from_den(user) + user.visible_message(SPAN_NOTICE("\The [user] emerges from \the [src].")) + +/obj/structure/animal_den/Destroy() + for(var/atom/movable/AM in contents) + remove_from_den(AM) + . = ..() + +/obj/structure/animal_den/attack_hand(mob/user) + if(try_grab_occupant(user)) + return TRUE + . = ..() + +/obj/structure/animal_den/attackby(obj/item/O, mob/user) + if(O.has_tool_quality(TOOL_SHOVEL) && try_collapse_den(user)) + return TRUE + . = ..() + +/obj/structure/animal_den/proc/try_grab_occupant(var/mob/user) + if(user.a_intent != I_GRAB) + return FALSE + if(!do_after(user, 1 SECOND, src) || QDELETED(src)) + return TRUE + if(!length(contents)) + to_chat(user, SPAN_WARNING("There is nothing inside \the [src].")) + return TRUE + var/atom/movable/AM = pick(contents) + remove_from_den(AM) + user.visible_message(SPAN_NOTICE("\The [user] reaches into \the [src] and pulls out \the [AM]!")) + update_icon() + return TRUE + +/obj/structure/animal_den/proc/try_collapse_den(var/mob/user) + if(user.a_intent != I_HURT) + return FALSE + // Humans can only get here via a shovel, so it's fine for them to be small. + if(user.mob_size < MOB_MEDIUM && !ishuman(user)) + to_chat(user, SPAN_WARNING("You are too small to collapse \the [src].")) + return TRUE + if(!do_after(user, 5 SECONDS, src) || QDELETED(src)) + return TRUE + if(length(contents)) + to_chat(user, SPAN_WARNING("You can't collapse \the [src]; it's occupied.")) + else + to_chat(user, SPAN_NOTICE("You collapse \the [src].")) + playsound(user, 'sound/weapons/thudswoosh.ogg', 50) + qdel(src) + return TRUE + +/obj/structure/animal_den/attack_generic(mob/user, damage, attack_verb) + // Animal interactions with their nests. + if(istype(user, /mob/living/simple_mob/animal)) + if(try_collapse_den(user)) + return TRUE + if(try_grab_occupant(user)) + return TRUE + if(!do_after(user, src, 1 SECOND)) + return TRUE + if(length(contents)) + to_chat(user, SPAN_WARNING("You can't hide in \the [src]; it's occupied.")) + return TRUE + user.visible_message(SPAN_NOTICE("\The [user] disappears into \the [src].")) + user.forceMove(src) + update_icon() + return TRUE + return ..() + +/mob/living/simple_mob/animal/proc/get_eye_color() + return COLOR_WHITE + +/obj/structure/animal_den/update_icon() + cut_overlays() + for(var/mob/living/simple_mob/animal/critter in contents) + if(critter.stat == CONSCIOUS && !critter.resting) + var/image/I = image(icon, "eyes[rand(1,3)]") + I.color = critter.get_eye_color() + I.pixel_x = rand(-3, 3) + I.pixel_y = rand(-3, 3) + add_overlay(I) + +// Joinable den for critter spawns with ghost players. +/obj/structure/animal_den/ghost_join + var/mob/living/critter + var/ban_check = "Critter" + var/welcome_text + +/obj/structure/animal_den/ghost_join/Initialize() + . = ..() + name = "den" // to remove mapping identifiers. + if(ispath(critter)) + critter = new critter(src) + +/obj/structure/animal_den/ghost_join/remove_from_den(var/atom/movable/AM) + . = ..() + if(critter == AM && critter.loc != src) + if(critter.resting) + critter.lay_down() // you woke them up :( + critter = null + +/obj/structure/animal_den/ghost_join/examine(mob/user, infix, suffix) + . = ..() + if(isobserver(user)) + if(critter) + if(ban_check && ckey_is_jobbanned(user.ckey, ban_check)) + to_chat(user, SPAN_WARNING("You are banned from [ban_check] roles and cannot join via this den.")) + else if(user.MayRespawn(TRUE)) + to_chat(user, SPAN_NOTICE("Click on the den to join as \a [critter].")) + else + to_chat(user, SPAN_WARNING("This den is no longer available for joining.")) + +/obj/structure/animal_den/ghost_join/attack_ghost(mob/user) + if(critter) + transfer_personality(user) + return + return ..() + +/obj/structure/animal_den/ghost_join/proc/transfer_personality(var/mob/user) + set waitfor = FALSE + if(!critter) + return + if(user.mind) + user.mind.transfer_to(critter) + else + critter.key = user.key + + var/mob/living/critter_ref = critter + critter = null + + // Sleep long enough for them to login and get any other text out of the way. + sleep(1) + to_chat(critter_ref, SPAN_NOTICE("You are \a [critter]!")) + if(welcome_text) + to_chat(critter_ref, SPAN_NOTICE(welcome_text)) + + // Sleep long enough for the logged-in critter to update state and regen icon. + sleep(SSmobs.wait) + if(critter_ref.resting) // get up lazybones + critter_ref.lay_down() + update_icon() diff --git a/code/game/objects/structures/flora/flora.dm b/code/game/objects/structures/flora/flora.dm index a39370bb7b..ddf70cd639 100644 --- a/code/game/objects/structures/flora/flora.dm +++ b/code/game/objects/structures/flora/flora.dm @@ -71,9 +71,9 @@ ..(W, user) -/obj/structure/flora/proc/can_harvest(var/obj/item/I) +/obj/structure/flora/proc/can_harvest(var/obj/item/I, var/ignore_tool = FALSE) . = FALSE - if(harvest_tool && istype(I, harvest_tool) && harvest_loot && harvest_loot.len && harvest_count < max_harvests) + if((ignore_tool || (harvest_tool && istype(I, harvest_tool))) && harvest_loot && harvest_loot.len && harvest_count < max_harvests) . = TRUE return . @@ -89,6 +89,33 @@ harvest_count++ return AM +/obj/structure/flora/attack_generic(mob/user, damage, attack_verb) + if(istype(user, /mob/living/simple_mob/animal) && user.a_intent != I_HURT) + var/mob/living/simple_mob/animal/critter = user + if(!critter.forager) + return ..() + if(!can_forage(critter)) + to_chat(critter, SPAN_WARNING("You cannot see any edible fruit on \the [src].")) + return TRUE + show_animal_foraging_message(critter) + if(!do_after(critter, 2 SECONDS, src) || QDELETED(src) || !can_forage(user)) + return TRUE + show_animal_eating_message(critter) + playsound(critter, 'sound/items/eatfood.ogg', rand(10,50), 1) + harvest_count++ + critter.eat_food_item(spawn_harvest(pickweight(harvest_loot))) + return TRUE + return ..() + +/obj/structure/flora/proc/can_forage(var/mob/critter) + return can_harvest(ignore_tool = TRUE) + +/obj/structure/flora/proc/show_animal_foraging_message(var/mob/critter) + critter.visible_message(SPAN_NOTICE("\The [critter] begins sniffing at \the [src].")) + +/obj/structure/flora/proc/show_animal_eating_message(var/mob/critter) + critter.visible_message(SPAN_NOTICE("\The [critter] rips away some of the fruit from \the [src].")) + //bushes /obj/structure/flora/bush name = "bush" diff --git a/code/game/objects/structures/flora/trees.dm b/code/game/objects/structures/flora/trees.dm index 6b8ca8c5d1..b7d70d64c6 100644 --- a/code/game/objects/structures/flora/trees.dm +++ b/code/game/objects/structures/flora/trees.dm @@ -30,9 +30,9 @@ /obj/structure/flora/tree/proc/choose_icon_state() return icon_state -/obj/structure/flora/tree/can_harvest(var/obj/item/I) +/obj/structure/flora/tree/can_harvest(var/obj/item/I, var/ignore_tool = FALSE) . = FALSE - if(!is_stump && harvest_tool && istype(I, harvest_tool) && harvest_loot && harvest_loot.len && harvest_count < max_harvests) + if(!is_stump && (ignore_tool || (harvest_tool && istype(I, harvest_tool))) && harvest_loot && harvest_loot.len && harvest_count < max_harvests) . = TRUE return . @@ -139,6 +139,12 @@ return results +/obj/structure/flora/tree/show_animal_foraging_message(var/mob/critter) + critter.visible_message(SPAN_NOTICE("\The [critter] begins searching through the foliage of \the [src].")) + +/obj/structure/flora/tree/show_animal_eating_message(var/mob/critter) + critter.visible_message(SPAN_NOTICE("\The [critter] pulls down some low-hanging fruit from \the [src].")) + // Subtypes. // Pine trees diff --git a/code/game/turfs/simulated/outdoors/outdoors.dm b/code/game/turfs/simulated/outdoors/outdoors.dm index 39bd4ca1b2..61eafef4d3 100644 --- a/code/game/turfs/simulated/outdoors/outdoors.dm +++ b/code/game/turfs/simulated/outdoors/outdoors.dm @@ -8,7 +8,7 @@ var/global/list/turf_edge_cache = list() var/outdoors = OUTDOORS_AREA /area - // If a turf's `outdoors` variable is set to `OUTDOORS_AREA`, + // If a turf's `outdoors` variable is set to `OUTDOORS_AREA`, // it will decide if it's outdoors or not when being initialized based on this var. var/outdoors = OUTDOORS_NO @@ -39,6 +39,30 @@ var/global/list/turf_edge_cache = list() if(can_dig && prob(33)) loot_count = rand(1,3) +/turf/simulated/floor/outdoors/attack_generic(mob/user) + if(isanimal(user) && user.loc == src && user.a_intent == I_HELP) + var/mob/living/simple_mob/animal/critter = user + if(critter.burrower) + if(locate(/obj/structure/animal_den) in contents) + to_chat(critter, SPAN_WARNING("There is already a den here.")) + return TRUE + critter.visible_message(SPAN_NOTICE("\The [critter] begins digging industriously.")) + if(!do_after(critter, 10 SECONDS, src)) + return TRUE + if(locate(/obj/structure/animal_den) in contents) + to_chat(critter, SPAN_WARNING("There is already a den here.")) + return TRUE + critter.visible_message(SPAN_NOTICE("\The [critter] finishes digging a den!")) + new /obj/structure/animal_den(src) + if(prob(66)) + var/worms = 0 + for(var/worm in 1 to rand(3)) + new /obj/item/reagent_containers/food/snacks/worm(src) + worms++ + to_chat(critter, SPAN_NOTICE("You unearthed [worms] worm\s!")) + return TRUE + . = ..() + /turf/simulated/floor/outdoors/attackby(obj/item/C, mob/user) if(can_dig && istype(C, /obj/item/shovel)) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index f4b522598e..29b59d4aed 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1640,4 +1640,25 @@ /mob/living/carbon/human/get_sound_volume_multiplier() . = ..() for(var/obj/item/clothing/C in list(l_ear, r_ear, head)) - . = min(., C.volume_multiplier) \ No newline at end of file + . = min(., C.volume_multiplier) + +/mob/living/carbon/human/rip_out_internal_organ(var/zone, var/skip_wounding = FALSE, var/damage_descriptor) + + var/obj/item/organ/organ + var/obj/item/organ/external/limb + if(zone) + limb = organs_by_name[zone] + if(limb && length(limb.internal_organs)) + organ = pick_n_take(limb.internal_organs) + if(!organ) + organ = ..(skip_wounding = TRUE) + + if(organ) + organ.removed() + if(organ.parent_organ && !limb) + limb = organs_by_name[organ.parent_organ] + if(limb) + limb.take_damage(rand(10,20), 0, TRUE, TRUE, damage_descriptor) + else + take_damage(rand(10, 20)) + return organ diff --git a/code/modules/mob/living/carbon/human/species/species_attack.dm b/code/modules/mob/living/carbon/human/species/species_attack.dm index 9be2a2f7e8..baa083da81 100644 --- a/code/modules/mob/living/carbon/human/species/species_attack.dm +++ b/code/modules/mob/living/carbon/human/species/species_attack.dm @@ -99,4 +99,4 @@ /datum/unarmed_attack/stomp/weak/show_attack(var/mob/living/carbon/human/user, var/mob/living/carbon/human/target, var/zone, var/attack_damage) var/obj/item/organ/external/affecting = target.get_organ(zone) user.visible_message("[user] jumped up and down on \the [target]'s [affecting.name]!") - playsound(user, attack_sound, 25, 1, -1) \ No newline at end of file + playsound(user, attack_sound, 25, 1, -1) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index d01f01e461..0188488c7f 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1143,7 +1143,7 @@ var/turf/T = get_turf(src) var/obj/effect/overmap/visitable/M = get_overmap_sector(T.z) - + if(istype(M)) if(M.in_space) if(T.z in using_map.station_levels) @@ -1156,3 +1156,10 @@ var/datum/map_z_level/zlevel = using_map.zlevels["[T.z]"] if(istype(zlevel)) . |= zlevel.event_regions + +// kali maaaaa +/mob/living/proc/rip_out_internal_organ(var/zone, var/skip_wounding = FALSE, var/damage_descriptor) + if(length(internal_organs)) + . = pick_n_take(internal_organs) + if(!skip_wounding) + take_damage(rand(10,20)) diff --git a/code/modules/mob/living/simple_mob/on_click.dm b/code/modules/mob/living/simple_mob/on_click.dm index 4b824c9580..1434373b80 100644 --- a/code/modules/mob/living/simple_mob/on_click.dm +++ b/code/modules/mob/living/simple_mob/on_click.dm @@ -11,32 +11,7 @@ var/obj/O = A return O.attack_hand(src) - switch(a_intent) - if(I_HELP) - if(isliving(A)) - custom_emote(1,"[pick(friendly)] \the [A]!") - - if(I_HURT) - if(can_special_attack(A) && special_attack_target(A)) - return - - else if(melee_damage_upper == 0 && istype(A,/mob/living)) - custom_emote(1,"[pick(friendly)] \the [A]!") - - else - attack_target(A) - - if(I_GRAB) - if(has_hands) - A.attack_hand(src) - else - attack_target(A) - - if(I_DISARM) - if(has_hands) - A.attack_hand(src) - else - attack_target(A) + return do_interaction(A) /mob/living/simple_mob/RangedAttack(var/atom/A) // setClickCooldown(get_attack_speed()) @@ -45,4 +20,40 @@ return if(projectiletype) - shoot_target(A) \ No newline at end of file + shoot_target(A) + +/mob/living/simple_mob/proc/do_interaction(var/atom/A) + switch(a_intent) + if(I_HURT) + return do_hurt_interaction(A) + if(I_GRAB) + return do_grab_interaction(A) + if(I_DISARM) + return do_disarm_interaction(A) + return do_help_interaction(A) + +/mob/living/simple_mob/proc/do_help_interaction(var/atom/A) + if(isliving(A)) + custom_emote(1,"[pick(friendly)] \the [A]!") + return TRUE + return FALSE + +/mob/living/simple_mob/proc/do_hurt_interaction(var/atom/A) + if(can_special_attack(A) && special_attack_target(A)) + return + else if(melee_damage_upper == 0 && istype(A,/mob/living)) + custom_emote(1,"[pick(friendly)] \the [A]!") + else + attack_target(A) + +/mob/living/simple_mob/proc/do_disarm_interaction(var/atom/A) + if(has_hands) + A.attack_hand(src) + else + attack_target(A) + +/mob/living/simple_mob/proc/do_grab_interaction(var/atom/A) + if(has_hands) + A.attack_hand(src) + else + attack_target(A) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm index fa3f58e219..8200f5f87a 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm @@ -23,5 +23,44 @@ /obj/item/stack/animalhide = 3\ ) + var/forager = TRUE // Can eat from trees and bushes. + var/scavenger = FALSE // Can eat from corpses. + var/burrower = FALSE // Can dig dens. + /decl/mob_organ_names/quadruped //Most subtypes have this basic body layout. - hit_zones = list("head", "torso", "left foreleg", "right foreleg", "left hind leg", "right hind leg", "tail") \ No newline at end of file + hit_zones = list("head", "torso", "left foreleg", "right foreleg", "left hind leg", "right hind leg", "tail") + +/mob/living/simple_mob/animal/do_interaction(var/atom/A) + var/static/list/atom_types_with_animal_interactions = list( + /obj/structure/animal_den, + /obj/structure/flora, + /turf/simulated/floor/outdoors + ) + for(var/checktype in atom_types_with_animal_interactions) + if(istype(A, checktype)) + attack_generic(A, 0, "investigates") + return TRUE + if(scavenger && isliving(A) && a_intent == I_HURT) + var/mob/living/M = A + if(M.stat == DEAD && length(M.internal_organs)) + to_chat(src, SPAN_NOTICE("You dig into the guts of \the [M], hunting for the sweetest meat.")) + if(do_after(src, M, 2 SECOND) && !QDELETED(M) && length(M.internal_organs)) + var/obj/item/organ = M.rip_out_internal_organ(check_zone(zone_sel.selecting), damage_descriptor = "animal teeth") + if(organ) + visible_message(SPAN_DANGER("\The [src] rips \the [organ] out of \the [M] and devours [organ.gender == PLURAL ? "them" : "it"]!")) + eat_food_item(organ) + if(!length(M.internal_organs) && M.gib_on_butchery) + M.gib() + return TRUE + return ..() + +/mob/living/simple_mob/animal/proc/eat_food_item(var/obj/item/snack, var/silent) + if(istype(snack, /obj/item/organ)) + var/obj/item/organ/organ = snack + if(organ.meat_type) + eat_food_item(new organ.meat_type(src), silent = TRUE) + if(!silent) + playsound(src, 'sound/items/eatfood.ogg', rand(10,50), 1) + if(snack.reagents?.total_volume) + snack.reagents.trans_to_mob(src, snack.reagents.total_volume, CHEM_INGEST) + qdel(snack) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm index 03a253d0b5..c0d3ad81d9 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm @@ -425,4 +425,4 @@ attack_speed_percent = 0.8 /decl/mob_organ_names/kururak - hit_zones = list("head", "chest", "left foreleg", "right foreleg", "left hind leg", "right hind leg", "far left tail", "far right tail", "left middle tail", "right middle tail") \ No newline at end of file + hit_zones = list("head", "chest", "left foreleg", "right foreleg", "left hind leg", "right hind leg", "far left tail", "far right tail", "left middle tail", "right middle tail") diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/siffet.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/siffet.dm index 40ae65d3bc..1e7a63f417 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/siffet.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/siffet.dm @@ -40,6 +40,7 @@ say_list_type = /datum/say_list/siffet ai_holder_type = /datum/ai_holder/simple_mob/siffet + burrower = TRUE /datum/say_list/siffet speak = list("Yap!", "Heh!", "Huff.") @@ -58,4 +59,8 @@ /mob/living/simple_mob/animal/sif/siffet/IIsAlly(mob/living/L) . = ..() if(!. && L.mob_size > 10) //Attacks things it considers small enough to take on, otherwise only attacks if attacked. - return TRUE \ No newline at end of file + return TRUE + +/obj/structure/animal_den/ghost_join/siffet + name = "siffet den" + critter = /mob/living/simple_mob/animal/sif/siffet diff --git a/icons/mob/64x32.dmi b/icons/mob/64x32.dmi index 615fd87a01..b40f672c2d 100644 Binary files a/icons/mob/64x32.dmi and b/icons/mob/64x32.dmi differ diff --git a/icons/obj/structures/den.dmi b/icons/obj/structures/den.dmi new file mode 100644 index 0000000000..8fe7d5ccc7 Binary files /dev/null and b/icons/obj/structures/den.dmi differ diff --git a/polaris.dme b/polaris.dme index 7325f79a9c..db9da46c5c 100644 --- a/polaris.dme +++ b/polaris.dme @@ -1178,6 +1178,7 @@ #include "code\game\objects\random\mob.dm" #include "code\game\objects\random\spacesuits.dm" #include "code\game\objects\random\unidentified\medicine.dm" +#include "code\game\objects\structures\animal_den.dm" #include "code\game\objects\structures\barricades.dm" #include "code\game\objects\structures\barsign.dm" #include "code\game\objects\structures\bedsheet_bin.dm"