mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
[BOUNTY] Adds echolocation (#19701)
Blind people can now echolocate Blindness quirk point reduction reduced to -6
This commit is contained in:
@@ -142,6 +142,7 @@
|
||||
#define BLIND_LAYER 20.3
|
||||
#define CRIT_LAYER 20.4
|
||||
#define CURSE_LAYER 20.5
|
||||
#define ECHO_LAYER 20.6
|
||||
#define FULLSCREEN_RENDER_TARGET "FULLSCREEN_PLANE"
|
||||
|
||||
#define HUD_PLANE 21
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
#define FIRE_PRIORITY_RESEARCH 10
|
||||
#define FIRE_PRIORITY_VIS 10
|
||||
#define FIRE_PRIORITY_GARBAGE 15
|
||||
#define FIRE_PRIORITY_ECHOLOCATION 15
|
||||
#define FIRE_PRIORITY_WET_FLOORS 20
|
||||
#define FIRE_PRIORITY_FLUIDS 20
|
||||
#define FIRE_PRIORITY_AIR 20
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
#define TRAIT_INCAPACITATED "incapacitated"
|
||||
#define HANDCUFFED_TRAIT "handcuffed"
|
||||
#define TRAIT_BLIND "blind"
|
||||
#define TRAIT_ECHOLOCATION_RECEIVER "echolocation_receiver"
|
||||
#define TRAIT_MUTE "mute"
|
||||
#define TRAIT_EMOTEMUTE "emotemute"
|
||||
#define TRAIT_DEAF "deaf"
|
||||
@@ -407,6 +408,7 @@
|
||||
#define CHANGESTING_TRAIT "changesting"
|
||||
#define POSIBRAIN_TRAIT "positrait"
|
||||
#define WRIST_STRAP_TRAIT "wrist_strap"
|
||||
#define ECHOLOCATION_TRAIT "echolocation_trait"
|
||||
|
||||
///Traits given by station traits
|
||||
#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
|
||||
|
||||
@@ -10,3 +10,12 @@
|
||||
.["render_source"] = render_source
|
||||
if(!isnull(flags))
|
||||
.["flags"] = flags
|
||||
|
||||
/proc/outline_filter(size, color, flags)
|
||||
. = list("type" = "outline")
|
||||
if(!isnull(size))
|
||||
.["size"] = size
|
||||
if(!isnull(color))
|
||||
.["color"] = color
|
||||
if(!isnull(flags))
|
||||
.["flags"] = flags
|
||||
|
||||
@@ -226,3 +226,8 @@
|
||||
icon_state = "blue_eye"
|
||||
plane = FULLSCREEN_PLANE
|
||||
layer = CURSE_LAYER
|
||||
|
||||
/atom/movable/screen/fullscreen/echolocation
|
||||
icon_state = "echo"
|
||||
layer = CAMERA_STATIC_LAYER
|
||||
plane = CAMERA_STATIC_PLANE
|
||||
|
||||
204
code/datums/components/echolocation.dm
Normal file
204
code/datums/components/echolocation.dm
Normal file
@@ -0,0 +1,204 @@
|
||||
/datum/component/echolocation
|
||||
///Default echo range
|
||||
var/default_echo_range = 4
|
||||
/// Current radius, will set itself to default
|
||||
var/echo_range
|
||||
/// Time between echolocations.
|
||||
var/cooldown_time = 2 SECONDS
|
||||
/// Time for the image to start fading out.
|
||||
var/image_expiry_time = 1.5 SECONDS
|
||||
/// Time for the image to fade in.
|
||||
var/fade_in_time = 0.5 SECONDS
|
||||
/// Time for the image to fade out and delete itself.
|
||||
var/fade_out_time = 0.5 SECONDS
|
||||
/// Are images static? If yes, spawns them on the turf and makes them not change location. Otherwise they change location and pixel shift with the original.
|
||||
var/images_are_static = TRUE
|
||||
/// With mobs that have this echo group in their echolocation receiver trait, we share echo images, defaults to quirk
|
||||
var/echo_group = null
|
||||
/// Color applied over the client
|
||||
var/client_color = null
|
||||
/// Associative list of world.time when created to a list of the images.
|
||||
var/list/images = list()
|
||||
/// Associative list of world.time when created to a list of receivers.
|
||||
var/list/receivers = list()
|
||||
/// All the saved appearances, keyed by icon-icon_state.
|
||||
var/static/list/saved_appearances = list()
|
||||
/// Typecache of all the allowed paths to render.
|
||||
var/static/list/allowed_paths
|
||||
/// Typecache of turfs that are dangerous, to give them a special icon.
|
||||
var/static/list/danger_turfs
|
||||
/// A matrix that turns everything except #ffffff into pure blackness, used for our images (the outlines are #ffffff).
|
||||
var/static/list/black_white_matrix = list(85, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, 0, 0, 0, 0, 1, -254, -254, -254, 0)
|
||||
/// A matrix that turns everything into pure white.
|
||||
var/static/list/white_matrix = list(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0, 1, 0, -0, 0, 0)
|
||||
|
||||
/// Cooldown for the echolocation.
|
||||
COOLDOWN_DECLARE(cooldown_last)
|
||||
|
||||
/datum/component/echolocation/Initialize(echo_range, cooldown_time, image_expiry_time, fade_in_time, fade_out_time, images_are_static, blocking_trait, echo_group, echo_icon, color_path)
|
||||
. = ..()
|
||||
var/mob/living/echolocator = parent
|
||||
if(!istype(echolocator))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if(!danger_turfs)
|
||||
danger_turfs = typecacheof(list(/turf/open/chasm, /turf/open/lava, /turf/open/floor/fakepit))
|
||||
if(!allowed_paths)
|
||||
allowed_paths = typecacheof(list(/turf/closed, /obj, /mob/living)) + danger_turfs - typecacheof(/obj/effect/decal)
|
||||
if(!isnull(echo_range))
|
||||
src.echo_range = default_echo_range
|
||||
if(!isnull(cooldown_time))
|
||||
src.cooldown_time = cooldown_time
|
||||
if(!isnull(image_expiry_time))
|
||||
src.image_expiry_time = image_expiry_time
|
||||
if(!isnull(fade_in_time))
|
||||
src.fade_in_time = fade_in_time
|
||||
if(!isnull(fade_out_time))
|
||||
src.fade_out_time = fade_out_time
|
||||
if(!isnull(images_are_static))
|
||||
src.images_are_static = images_are_static
|
||||
if(ispath(color_path))
|
||||
client_color = echolocator.add_client_colour(color_path)
|
||||
else
|
||||
client_color = echolocator.add_client_colour(/datum/client_colour/echolocate)
|
||||
|
||||
src.echo_group = echo_group || REF(src)
|
||||
echolocator.add_traits(list(TRAIT_ECHOLOCATION_RECEIVER, TRAIT_NIGHT_VISION), src.echo_group) //so they see all the tiles they echolocated, even if they are in the dark
|
||||
echolocator.become_blind(ECHOLOCATION_TRAIT)
|
||||
echolocator.overlay_fullscreen("echo", /atom/movable/screen/fullscreen/echo)
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/datum/component/echolocation/Destroy(force, silent)
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
var/mob/living/echolocator = parent
|
||||
echolocator.remove_traits(list(TRAIT_ECHOLOCATION_RECEIVER, TRAIT_NIGHT_VISION), echo_group)
|
||||
echolocator.cure_blind(ECHOLOCATION_TRAIT)
|
||||
echolocator.clear_fullscreen("echo")
|
||||
for(var/timeframe in images)
|
||||
delete_images(timeframe)
|
||||
return ..()
|
||||
|
||||
/datum/component/echolocation/process()
|
||||
var/mob/living/echolocator = parent
|
||||
if(echolocator.stat == DEAD)
|
||||
return
|
||||
echolocate()
|
||||
|
||||
/datum/component/echolocation/proc/echolocate()
|
||||
if(!COOLDOWN_FINISHED(src, cooldown_last))
|
||||
return
|
||||
COOLDOWN_START(src, cooldown_last, cooldown_time)
|
||||
var/mob/living/echolocator = parent
|
||||
echo_range = echo_sound_environment(echolocator, default_echo_range)
|
||||
var/list/filtered = list()
|
||||
var/list/seen = dview(echo_range, get_turf(echolocator.client?.eye || echolocator), invis_flags = echolocator.see_invisible)
|
||||
for(var/atom/seen_atom as anything in seen)
|
||||
if(!seen_atom.alpha)
|
||||
continue
|
||||
if(allowed_paths[seen_atom.type])
|
||||
filtered += seen_atom
|
||||
if(!length(filtered))
|
||||
return
|
||||
var/current_time = "[world.time]"
|
||||
images[current_time] = list()
|
||||
receivers[current_time] = list()
|
||||
for(var/mob/living/viewer in filtered)
|
||||
if(HAS_TRAIT_FROM(viewer, TRAIT_ECHOLOCATION_RECEIVER, echo_group))
|
||||
receivers[current_time] += viewer
|
||||
for(var/atom/filtered_atom as anything in filtered)
|
||||
show_image(saved_appearances["[filtered_atom.icon]-[filtered_atom.icon_state]"] || generate_appearance(filtered_atom), filtered_atom, current_time)
|
||||
addtimer(CALLBACK(src, PROC_REF(fade_images), current_time), image_expiry_time)
|
||||
|
||||
/datum/component/echolocation/proc/echo_sound_environment(mob/living/creature, range)
|
||||
var/area/A = get_area(creature)
|
||||
var/sound_environment = A.sound_environment
|
||||
switch(sound_environment)
|
||||
if(SOUND_AREA_SPACE)
|
||||
return range -3
|
||||
if(SOUND_AREA_STANDARD_STATION)
|
||||
return range
|
||||
if(SOUND_AREA_LARGE_ENCLOSED)
|
||||
return range -1
|
||||
if(SOUND_AREA_SMALL_ENCLOSED)
|
||||
return range +1
|
||||
if(SOUND_AREA_TUNNEL_ENCLOSED)
|
||||
return range +2
|
||||
if(SOUND_AREA_LARGE_SOFTFLOOR)
|
||||
return range -1
|
||||
if(SOUND_AREA_ASTEROID)
|
||||
return range -2
|
||||
if(SOUND_AREA_LAVALAND)
|
||||
return range -1
|
||||
if(SOUND_AREA_WOODFLOOR)
|
||||
return range +1
|
||||
else
|
||||
return range
|
||||
|
||||
/datum/component/echolocation/proc/show_image(image/input_appearance, atom/input, current_time)
|
||||
var/image/final_image = image(input_appearance)
|
||||
final_image.layer += EFFECTS_LAYER
|
||||
final_image.plane = FULLSCREEN_PLANE
|
||||
final_image.loc = images_are_static ? get_turf(input) : input
|
||||
final_image.dir = input.dir
|
||||
final_image.alpha = 0
|
||||
if(images_are_static)
|
||||
final_image.pixel_x = input.pixel_x
|
||||
final_image.pixel_y = input.pixel_y
|
||||
if(HAS_TRAIT_FROM(input, TRAIT_ECHOLOCATION_RECEIVER, echo_group)) //mark other echolocation with full white
|
||||
final_image.color = white_matrix
|
||||
images[current_time] += final_image
|
||||
for(var/mob/living/echolocate_receiver as anything in receivers[current_time])
|
||||
if(echolocate_receiver == input)
|
||||
continue
|
||||
if(echolocate_receiver.client)
|
||||
echolocate_receiver.client.images += final_image
|
||||
animate(final_image, alpha = 255, time = fade_in_time)
|
||||
|
||||
/datum/component/echolocation/proc/generate_appearance(atom/input)
|
||||
var/use_outline = TRUE
|
||||
var/mutable_appearance/copied_appearance = new /mutable_appearance()
|
||||
copied_appearance.appearance = input
|
||||
if(istype(input, /obj/machinery/door/airlock)) //i hate you
|
||||
copied_appearance.cut_overlays()
|
||||
copied_appearance.icon = 'icons/obj/doors/airlocks/station/public.dmi'
|
||||
copied_appearance.icon_state = "closed"
|
||||
else if(danger_turfs[input.type])
|
||||
copied_appearance.icon = 'icons/turf/floors.dmi'
|
||||
copied_appearance.icon_state = "danger"
|
||||
use_outline = FALSE
|
||||
copied_appearance.color = black_white_matrix
|
||||
if(use_outline)
|
||||
copied_appearance.filters += outline_filter(size = 1, color = COLOR_WHITE)
|
||||
if(!images_are_static)
|
||||
copied_appearance.pixel_x = 0
|
||||
copied_appearance.pixel_y = 0
|
||||
copied_appearance.transform = matrix()
|
||||
if(!iscarbon(input)) //wacky overlay people get generated everytime
|
||||
saved_appearances["[input.icon]-[input.icon_state]"] = copied_appearance
|
||||
return copied_appearance
|
||||
|
||||
/datum/component/echolocation/proc/fade_images(from_when)
|
||||
for(var/image_echo in images[from_when])
|
||||
animate(image_echo, alpha = 0, time = fade_out_time)
|
||||
addtimer(CALLBACK(src, PROC_REF(delete_images), from_when), fade_out_time)
|
||||
|
||||
/datum/component/echolocation/proc/delete_images(from_when)
|
||||
for(var/mob/living/echolocate_receiver as anything in receivers[from_when])
|
||||
if(!echolocate_receiver.client)
|
||||
continue
|
||||
for(var/image_echo in images[from_when])
|
||||
echolocate_receiver.client.images -= image_echo
|
||||
images -= from_when
|
||||
receivers -= from_when
|
||||
|
||||
/atom/movable/screen/fullscreen/echo
|
||||
icon_state = "echo"
|
||||
layer = ECHO_LAYER
|
||||
show_when_dead = TRUE
|
||||
|
||||
/atom/movable/screen/fullscreen/echo/Initialize(mapload)
|
||||
. = ..()
|
||||
particles = new /particles/echo()
|
||||
|
||||
/atom/movable/screen/fullscreen/echo/Destroy()
|
||||
QDEL_NULL(particles)
|
||||
return ..()
|
||||
@@ -49,13 +49,13 @@
|
||||
name = "Blind"
|
||||
desc = "You are completely blind, nothing can counteract this."
|
||||
icon = "eye-slash"
|
||||
value = -9
|
||||
value = -6
|
||||
gain_text = span_danger("You can't see anything.")
|
||||
lose_text = span_notice("You miraculously gain back your vision.")
|
||||
medical_record_text = "Patient has permanent blindness."
|
||||
|
||||
/datum/quirk/blindness/add()
|
||||
quirk_holder.become_blind(ROUNDSTART_TRAIT)
|
||||
quirk_holder.AddComponent(/datum/component/echolocation)
|
||||
|
||||
/datum/quirk/blindness/on_spawn()
|
||||
var/mob/living/carbon/human/H = quirk_holder
|
||||
|
||||
13
code/game/objects/effects/echo.dm
Normal file
13
code/game/objects/effects/echo.dm
Normal file
@@ -0,0 +1,13 @@
|
||||
/particles/echo
|
||||
icon = 'icons/effects/echo.dmi'
|
||||
icon_state = list("echo1" = 1, "echo2" = 1, "echo3" = 2)
|
||||
width = 480
|
||||
height = 480
|
||||
count = 1000
|
||||
spawning = 0.5
|
||||
lifespan = 2 SECONDS
|
||||
fade = 1 SECONDS
|
||||
gravity = list(0, -0.1)
|
||||
position = generator("box", list(-240, -240), list(240, 240), NORMAL_RAND)
|
||||
drift = generator("vector", list(-0.1, 0), list(0.1, 0))
|
||||
rotation = generator("num", 0, 360, NORMAL_RAND)
|
||||
@@ -775,7 +775,7 @@
|
||||
name = "gibtonite deposit"
|
||||
desc = "An active gibtonite reserve. Run!"
|
||||
stage = GIBTONITE_ACTIVE
|
||||
visible_message(span_danger("There was gibtonite inside! It's going to explode!"))
|
||||
visible_message(span_danger("There was gibtonite inside! It's going to explode!"), blind_message = span_danger("You hear an evil hissing!"))
|
||||
|
||||
var/notify_admins = 0
|
||||
if(z != 5)
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
var/colour = "" //Any client.color-valid value
|
||||
var/priority = 1 //Since only one client.color can be rendered on screen, we take the one with the highest priority value:
|
||||
//eg: "Bloody screen" > "goggles colour" as the former is much more important
|
||||
|
||||
|
||||
/mob
|
||||
var/list/client_colours = list()
|
||||
|
||||
@@ -116,3 +114,8 @@
|
||||
/datum/client_colour/monochrome_infra
|
||||
colour = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
|
||||
priority = INFINITY //we can't see colors anyway!
|
||||
|
||||
/datum/client_colour/echolocate
|
||||
colour = "#25a5ea"
|
||||
priority = 1
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
if(!eye_blind)
|
||||
clear_alert("blind")
|
||||
clear_fullscreen("blind")
|
||||
var/datum/component/echo_component = GetComponent(/datum/component/echolocation)
|
||||
if (echo_component)
|
||||
echo_component.Destroy()
|
||||
/**
|
||||
* Force set the blindness of a mob to some level
|
||||
*/
|
||||
@@ -70,6 +73,9 @@
|
||||
if(!eye_blind)
|
||||
clear_alert("blind")
|
||||
clear_fullscreen("blind")
|
||||
var/datum/component/echo_component = GetComponent(/datum/component/echolocation)
|
||||
if (echo_component)
|
||||
echo_component.Destroy()
|
||||
|
||||
/**
|
||||
* Make the mobs vision blurry
|
||||
|
||||
BIN
icons/effects/echo.dmi
Normal file
BIN
icons/effects/echo.dmi
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 261 B |
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 MiB After Width: | Height: | Size: 5.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 352 KiB |
@@ -498,6 +498,7 @@
|
||||
#include "code\datums\components\decal.dm"
|
||||
#include "code\datums\components\earhealing.dm"
|
||||
#include "code\datums\components\earprotection.dm"
|
||||
#include "code\datums\components\echolocation.dm"
|
||||
#include "code\datums\components\edit_complainer.dm"
|
||||
#include "code\datums\components\empprotection.dm"
|
||||
#include "code\datums\components\explodable.dm"
|
||||
@@ -1028,6 +1029,7 @@
|
||||
#include "code\game\objects\effects\contraband.dm"
|
||||
#include "code\game\objects\effects\countdown.dm"
|
||||
#include "code\game\objects\effects\custom_portals.dm"
|
||||
#include "code\game\objects\effects\echo.dm"
|
||||
#include "code\game\objects\effects\effects.dm"
|
||||
#include "code\game\objects\effects\forcefields.dm"
|
||||
#include "code\game\objects\effects\glowshroom.dm"
|
||||
|
||||
Reference in New Issue
Block a user