[MIRROR] convert the eyeball a basic monster [MDB IGNORE] (#23043)

* convert the eyeball a basic monster (#77411)

## About The Pull Request
I have created a basic eyeball monster with new abilities and behaviors.
The eyeball has a unique power that allows it to glare at humans and
make them slow for a short period. However, this ability only works if
the human can see the eyeball monster. If a person is blind or unable to
see the eyeball, the ability won't affect them. Also, if someone turns
their back to the eyeball, it cannot use the ability on them. But be
cautious because the eyeball will try to position itself in front of the
person's face to use its power.

The eyeball is hostile towards all humans except for the blind ones and
those with significant eye damage. It has a compassionate side too, as
it loves to help people with eye damage by providing small healing to
their eyes.

Furthermore, the eyeball has a fondness for eating carrots, which not
only satisfies its appetite but also grants it a small health boost. To
add to its appearance, I've given it a new, larger, and scarier sprite.
However, I am open to changing it back to the old sprite if the player
prefers it that way.

Additionally, the eyeball displays emotions, and if you hit it, it will
cry tears as a sign of pain or sadness.
![eyeballs
pictures](https://github.com/tgstation/tgstation/assets/138636438/8933ea63-d339-474b-8c6e-90a222b74945)

## Why It's Good For The Game
the eyeball now have more depth and character to his behavier.

## Changelog
🆑
refactor: the eyeball is a basic monster, please report any bugs
sprites: the eyeball now is bigger and scarier and now he will cry when
u hit him
/🆑

* convert the eyeball a basic monster

---------

Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-08-12 17:53:18 -04:00
committed by GitHub
co-authored by Ben10Omintrix
parent d4612dec4e
commit d4074241c1
11 changed files with 327 additions and 47 deletions
@@ -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
@@ -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
@@ -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
@@ -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