Skip to content

Commit

Permalink
convert the eyeball a basic monster (tgstation#77411)
Browse files Browse the repository at this point in the history
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)

the eyeball now have more depth and character to his behavier.

:cl:
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
/:cl:
  • Loading branch information
Ben10Omintrix authored and dwasint committed Aug 22, 2023
1 parent a683c02 commit 01e034a
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 46 deletions.
4 changes: 2 additions & 2 deletions _maps/shuttles/emergency_narnar.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
13 changes: 13 additions & 0 deletions code/__DEFINES/ai/monsters.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,16 @@
#define BB_LOBSTROSITY_TARGET_LIMB "BB_lobstrosity_target_limb"
/// 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"
///the hivebot partner we will go communicate with
#define BB_HIVE_PARTNER "BB_hive_partner"
123 changes: 123 additions & 0 deletions code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
43 changes: 0 additions & 43 deletions code/modules/mob/living/simple_animal/hostile/eyeballs.dm

This file was deleted.

Binary file modified icons/mob/simple/carp.dmi
Binary file not shown.
Loading

0 comments on commit 01e034a

Please sign in to comment.