diff --git a/_maps/shuttles/emergency_narnar.dmm b/_maps/shuttles/emergency_narnar.dmm index f05479065bc..835a6054a87 100644 --- a/_maps/shuttles/emergency_narnar.dmm +++ b/_maps/shuttles/emergency_narnar.dmm @@ -178,14 +178,14 @@ /turf/open/floor/cult, /area/shuttle/escape) "J" = ( -/mob/living/simple_animal/hostile/eyeball{ +/mob/living/basic/eyeball{ faction = list("cult"); name = "left eyeball" }, /turf/open/floor/cult, /area/shuttle/escape) "K" = ( -/mob/living/simple_animal/hostile/eyeball{ +/mob/living/basic/eyeball{ faction = list("cult"); name = "right eyeball" }, diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 128573c8b01..76de4ac8079 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -59,6 +59,13 @@ /// We increment this counter every time we try to move while dragging an arm and if we go too long we'll give up trying to get out of line of sight and just eat the fingers #define BB_LOBSTROSITY_FINGER_LUST "BB_lobstrosity_finger_lust" +///eyeball keys +///the death glare ability +#define BB_GLARE_ABILITY "BB_glare_ability" +///the blind target we must protect +#define BB_BLIND_TARGET "BB_blind_target" +///value to store the minimum eye damage to prevent us from attacking a human +#define BB_EYE_DAMAGE_THRESHOLD "BB_eye_damage_threshold" ///hivebot keys ///the machine we must go to repair #define BB_MACHINE_TARGET "BB_machine_target" diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm new file mode 100644 index 00000000000..47e43704079 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm @@ -0,0 +1,123 @@ +/mob/living/basic/eyeball + name = "eyeball" + desc = "An odd looking creature, it won't stop staring..." + icon = 'icons/mob/simple/carp.dmi' + icon_state = "eyeball" + icon_living = "eyeball" + icon_gib = "" + gender = NEUTER + gold_core_spawnable = HOSTILE_SPAWN + basic_mob_flags = DEL_ON_DEATH + gender = NEUTER + mob_biotypes = MOB_ORGANIC + + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + + maxHealth = 30 + health = 30 + obj_damage = 10 + melee_damage_lower = 8 + melee_damage_upper = 12 + + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + + faction = list(FACTION_SPOOKY) + speak_emote = list("telepathically cries") + + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = T0C + maximum_survivable_temperature = T0C + 1500 + sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS + + lighting_cutoff_red = 40 + lighting_cutoff_green = 20 + lighting_cutoff_blue = 30 + + ai_controller = /datum/ai_controller/basic_controller/eyeball + ///how much we will heal eyes + var/healing_factor = 3 + /// is this eyeball crying? + var/crying = FALSE + /// the crying overlay we add when is hit + var/mutable_appearance/on_hit_overlay + + ///cooldown to heal eyes + COOLDOWN_DECLARE(eye_healing) + +/mob/living/basic/eyeball/Initialize(mapload) + . = ..() + var/datum/action/cooldown/spell/pointed/death_glare/glare = new(src) + glare.Grant(src) + ai_controller.set_blackboard_key(BB_GLARE_ABILITY, glare) + AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 100, after_tame = CALLBACK(src, PROC_REF(on_tame))) + ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + on_hit_overlay = mutable_appearance(icon, "[icon_state]_crying") + +/mob/living/basic/eyeball/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + if(!.) + return + + if(!proximity_flag) + return + + if(istype(attack_target, /obj/item/food/grown/carrot)) + adjustBruteLoss(-5) + to_chat(src, span_warning("You eat [attack_target]! It restores some health!")) + qdel(attack_target) + return TRUE + +/mob/living/basic/eyeball/attackby(obj/item/weapon, mob/living/carbon/human/user, list/modifiers) + . = ..() + if(!weapon.force && !user.combat_mode) + return + if(crying) + return + change_crying_state() + addtimer(CALLBACK(src, PROC_REF(change_crying_state)), 10 SECONDS) //cry for 10 seconds then remove + +/mob/living/basic/eyeball/proc/change_crying_state() + crying = !crying + if(crying) + add_overlay(on_hit_overlay) + return + cut_overlay(on_hit_overlay) + + +/mob/living/basic/eyeball/proc/pre_attack(mob/living/eyeball, atom/target) + SIGNAL_HANDLER + + if(!ishuman(target)) + return + + var/mob/living/carbon/human_target = target + var/obj/item/organ/internal/eyes/eyes = human_target.get_organ_slot(ORGAN_SLOT_EYES) + if(!eyes) + return + if(eyes.damage < 10) + return + heal_eye_damage(human_target, eyes) + return COMPONENT_HOSTILE_NO_ATTACK + + +/mob/living/basic/eyeball/proc/heal_eye_damage(mob/living/target, obj/item/organ/internal/eyes/eyes) + if(!COOLDOWN_FINISHED(src, eye_healing)) + return + to_chat(target, span_warning("[src] seems to be healing your [eyes.zone]!")) + eyes.apply_organ_damage(-1 * healing_factor) + new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN) + befriend(target) + COOLDOWN_START(src, eye_healing, 15 SECONDS) + +/mob/living/basic/eyeball/proc/on_tame(mob/tamer) + spin(spintime = 2 SECONDS, speed = 1) + //become passive to the humens + faction |= tamer.faction diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ability.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ability.dm new file mode 100644 index 00000000000..734c385cf72 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ability.dm @@ -0,0 +1,40 @@ +/datum/action/cooldown/spell/pointed/death_glare + name = "death glare" + desc = "give a death stare to the victim" + var/glare_outline = COLOR_DARK_RED + spell_requirements = NONE + cooldown_time = 10 SECONDS + +/datum/action/cooldown/spell/pointed/death_glare/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + to_chat(owner, span_warning("Only living things are affected by our glare!")) + return FALSE + var/mob/living/living_target = cast_on + if(living_target.has_movespeed_modifier(/datum/movespeed_modifier/glare_slowdown)) + to_chat(owner, span_warning("This target is already affected by a glare!")) + return FALSE + if(!can_see(living_target, owner, 9)) + to_chat(owner, span_warning("This target cannot see our glare!")) + return FALSE + var/direction_to_compare = get_dir(living_target, owner) + var/target_direction = living_target.dir + if(direction_to_compare != target_direction) + to_chat(owner, span_warning("This target is facing away from us!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/death_glare/cast(mob/living/cast_on) + . = ..() + cast_on.add_filter("glare", 2, list("type" = "outline", "color" = glare_outline, "size" = 1)) + cast_on.add_movespeed_modifier(/datum/movespeed_modifier/glare_slowdown) + to_chat(cast_on, span_warning("You feel something watching you...")) + addtimer(CALLBACK(src, PROC_REF(remove_effect), cast_on), 5 SECONDS) + return TRUE + +/datum/action/cooldown/spell/pointed/death_glare/proc/remove_effect(mob/living/cast_on) + cast_on.remove_movespeed_modifier(/datum/movespeed_modifier/glare_slowdown) + cast_on.remove_filter("glare") + +/datum/movespeed_modifier/glare_slowdown + multiplicative_slowdown = 3 diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm new file mode 100644 index 00000000000..57ea39c94dd --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm @@ -0,0 +1,94 @@ +/datum/ai_behavior/find_the_blind + +/datum/ai_behavior/find_the_blind/perform(seconds_per_tick, datum/ai_controller/controller, blind_key, threshold_key) + . = ..() + + var/mob/living_pawn = controller.pawn + var/list/blind_list = list() + var/eye_damage_threshold = controller.blackboard[threshold_key] + if(!eye_damage_threshold) + finish_action(controller, FALSE) + return + for(var/mob/living/carbon/blind in oview(9, living_pawn)) + var/obj/item/organ/internal/eyes/eyes = blind.get_organ_slot(ORGAN_SLOT_EYES) + if(isnull(eyes)) + continue + if(eyes.damage < eye_damage_threshold) + continue + blind_list += blind + + if(!length(blind_list)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(blind_key, pick(blind_list)) + finish_action(controller, TRUE) + +/datum/ai_behavior/heal_eye_damage + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/heal_eye_damage/setup(datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/carbon/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/heal_eye_damage/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + + var/mob/living/carbon/target = controller.blackboard[target_key] + var/mob/living/living_pawn = controller.pawn + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + var/obj/item/organ/internal/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) + var/datum/callback/callback = CALLBACK(living_pawn, TYPE_PROC_REF(/mob/living/basic/eyeball, heal_eye_damage), target, eyes) + callback.Invoke() + + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/heal_eye_damage/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/targeted_mob_ability/glare_at_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + required_distance = 0 + +/datum/ai_behavior/targeted_mob_ability/glare_at_target/setup(datum/ai_controller/controller, ability_key, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if (isnull(target)) + return FALSE + + var/turf/turf_to_move_towards = get_step(target, target.dir) + if(turf_to_move_towards.is_blocked_turf(ignore_atoms = list(controller.pawn))) + return FALSE + + if(isnull(turf_to_move_towards)) + return FALSE + + set_movement_target(controller, turf_to_move_towards) + +/datum/ai_behavior/targeted_mob_ability/glare_at_target/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + var/mob/living/target = controller.blackboard[target_key] + + if(QDELETED(ability) || QDELETED(target)) + finish_action(controller, FALSE, ability_key, target_key) + return + + var/direction_to_compare = get_dir(target, controller.pawn) + var/target_direction = target.dir + if(direction_to_compare != target_direction) + finish_action(controller, FALSE, ability_key, target_key) + return + + var/result = ability.InterceptClickOn(controller.pawn, null, target) + finish_action(controller, result, ability_key, target_key) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/carrot + hunt_cooldown = 2 SECONDS + always_reset_target = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm new file mode 100644 index 00000000000..d20873f0ce9 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm @@ -0,0 +1,56 @@ +/datum/ai_controller/basic_controller/eyeball + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/eyeball, + BB_EYE_DAMAGE_THRESHOLD = 10, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/glare, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/heal_the_blind, + /datum/ai_planning_subtree/find_and_hunt_target/carrot, + ) + +/datum/ai_planning_subtree/heal_the_blind + +/datum/ai_planning_subtree/heal_the_blind/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/carbon/target = controller.blackboard[BB_BLIND_TARGET] + + if(QDELETED(target)) + controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD) + return + + controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/targetting_datum/basic/eyeball/can_attack(mob/living/owner, atom/target) + . = ..() + if(!.) + return FALSE + if(!ishuman(target)) + return TRUE + var/mob/living/carbon/human_target = target + if(human_target.is_blind()) + return FALSE + var/eye_damage_threshold = owner.ai_controller.blackboard[BB_EYE_DAMAGE_THRESHOLD] + if(!eye_damage_threshold) + return TRUE + var/obj/item/organ/internal/eyes/eyes = human_target.get_organ_slot(ORGAN_SLOT_EYES) + if(eyes.damage > eye_damage_threshold) //we dont attack people with bad vision + return FALSE + + return can_see(target, owner, 9) //if the target cant see us dont attack him + +/datum/ai_planning_subtree/targeted_mob_ability/glare + ability_key = BB_GLARE_ABILITY + use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/glare_at_target + finish_planning = TRUE + +/datum/ai_planning_subtree/find_and_hunt_target/carrot + target_key = BB_LOW_PRIORITY_HUNTING_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/carrot + hunt_targets = list(/obj/item/food/grown/carrot) + hunt_range = 6 diff --git a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm b/code/modules/mob/living/simple_animal/hostile/eyeballs.dm deleted file mode 100644 index 8c78685517a..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm +++ /dev/null @@ -1,43 +0,0 @@ -/mob/living/simple_animal/hostile/eyeball - name = "eyeball" - desc = "An odd looking creature, it won't stop staring..." - icon = 'icons/mob/simple/carp.dmi' - icon_state = "eyeball" - icon_living = "eyeball" - icon_gib = "" - maxHealth = 30 - health = 30 - harm_intent_damage = 15 - obj_damage = 10 - melee_damage_lower = 8 - melee_damage_upper = 12 - speed = 0 - gender = NEUTER - mob_biotypes = MOB_ORGANIC - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - emote_taunt = list("glares") - taunt_chance = 25 - speak_emote = list("telepathically cries") - turns_per_move = 5 - attack_verb_continuous = "blinks at" - attack_verb_simple = "blink at" - attack_sound = 'sound/weapons/pierce.ogg' - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - gold_core_spawnable = HOSTILE_SPAWN - faction = list(FACTION_SPOOKY) - del_on_death = 1 - // Redish ethereal glow. These lads live on the cult ship - lighting_cutoff_red = 40 - lighting_cutoff_green = 20 - lighting_cutoff_blue = 30 - sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS - -/mob/living/simple_animal/hostile/eyeball/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index e902fd3d4e2..16761be5b7e 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -116,7 +116,6 @@ /mob/living/simple_animal/hostile/construct/wraith/mystic, /mob/living/simple_animal/hostile/construct/wraith/noncult, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/eyeball, /mob/living/simple_animal/hostile/gorilla, /mob/living/simple_animal/hostile/gorilla/lesser, /mob/living/simple_animal/hostile/gorilla/cargo_domestic, diff --git a/icons/mob/simple/carp.dmi b/icons/mob/simple/carp.dmi index 54b9cb7bee4..1be59c43ecb 100644 Binary files a/icons/mob/simple/carp.dmi and b/icons/mob/simple/carp.dmi differ diff --git a/tgstation.dme b/tgstation.dme index b0980048d04..d00ba37f2ea 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4254,6 +4254,10 @@ #include "code\modules\mob\living\basic\space_fauna\demon\demon.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon_items.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon_subtypes.dm" +#include "code\modules\mob\living\basic\space_fauna\eyeball\_eyeball.dm" +#include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ability.dm" +#include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ai_behavior.dm" +#include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ai_subtree.dm" #include "code\modules\mob\living\basic\space_fauna\hivebot\_hivebot.dm" #include "code\modules\mob\living\basic\space_fauna\hivebot\hivebot_behavior.dm" #include "code\modules\mob\living\basic\space_fauna\hivebot\hivebot_subtree.dm" @@ -4493,7 +4497,6 @@ #include "code\modules\mob\living\simple_animal\hostile\blobbernaut.dm" #include "code\modules\mob\living\simple_animal\hostile\blobspore.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" -#include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" #include "code\modules\mob\living\simple_animal\hostile\heretic_monsters.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\illusion.dm" diff --git a/tools/UpdatePaths/Scripts/77410_eyeballs.txt b/tools/UpdatePaths/Scripts/77410_eyeballs.txt new file mode 100644 index 00000000000..767c0dd4235 --- /dev/null +++ b/tools/UpdatePaths/Scripts/77410_eyeballs.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/eyeball : /mob/living/basic/eyeball{@OLD}