Hologram Touchup (Init savings edition) (#74793)

## About The Pull Request

### Polishes and Reworks Holograms

Hologram generation currently involves a bunch of icon operations, which
are slow.
Not to mention a series of get flats for the human models, which is even
worse.

We lose 0.05 seconds of init to em off just the 2 RCD holograms. it
hurts man.

So instead, let's use filters and render steps to achive the same
effect.

While I'm here I'll dim the holo light and make it blue, make the
hologram and its beam emissive (so they glow), and do some fenangling
with move_hologram() (it doesn't clear the hologram off failure anymore,
instead relying on callers to do that) to ensure holocalls can't be
accidentially ended by moving out of the area.

Ah and I added RESET_ALPHA to the emissive appearance flags, cause the
alpha does override and fuck with color rendering, which ends up looking
dumb. If we're gonna support this stuff it should be first class not
accidential.

### Makes Static Not Shit

While I'm here (since holograms see static) lets ensure the static plane
is always visible if you're seeing through an ai eye.

The old solution was limited to applying it to JUST ais, which isn't
satisfactory for this sort of thing and missed a LOT of cases (I didn't
really get how ai eyes worked before I'ma be honest)

I'm adding a signal off the hud for it detecting a change in its eye
here.
This is semi redundant, but avoids unneeded dupe work, so I'm ok with
it.

The pipeline here is less sane then I'd like, but it works and that's
enough

## Why It's Good For The Game


![dreamseeker_zMiLXzlZ2X](https://user-images.githubusercontent.com/58055496/232470136-add945da-5f76-469e-ba1a-6ed3159b6f5b.png)
More pretty, better ux, **static works**

## Changelog
🆑
add: Holograms glow now, pokes at the lighting for holocalls in general
a bit to make em nicer.
qol: You can no longer accidentally end a holocall (as a non ai) by
leaving the area. Felt like garbage
fix: Fixes static rendering improperly if viewed by a non ai
/🆑
This commit is contained in:
LemonInTheDark
2023-04-18 18:25:52 -06:00
committed by GitHub
parent 783c673eac
commit 2b2cb3dff6
12 changed files with 174 additions and 96 deletions
@@ -1,2 +1,5 @@
/// Sent from /datum/hud/proc/on_eye_change(): (atom/old_eye, atom/new_eye)
#define COMSIG_HUD_EYE_CHANGED "hud_eye_changed"
/// Sent from /datum/hud/proc/eye_z_changed() : (old_offset, new_offset)
#define COMSIG_HUD_OFFSET_CHANGED "hud_offset_changed"
+2 -1
View File
@@ -76,7 +76,8 @@ GLOBAL_LIST_INIT(emissive_color, EMISSIVE_COLOR)
/// A globaly cached version of [EM_BLOCK_COLOR] for quick access.
GLOBAL_LIST_INIT(em_block_color, EM_BLOCK_COLOR)
/// A set of appearance flags applied to all emissive and emissive blocker overlays.
#define EMISSIVE_APPEARANCE_FLAGS (KEEP_APART|KEEP_TOGETHER|RESET_COLOR)
/// KEEP_APART to prevent parent hooking, KEEP_TOGETHER for children, and we reset the color and alpha of our parent so nothing gets overriden
#define EMISSIVE_APPEARANCE_FLAGS (KEEP_APART|KEEP_TOGETHER|RESET_COLOR|RESET_ALPHA)
/// The color matrix used to mask out emissive blockers on the emissive plane. Alpha should default to zero, be solely dependent on the RGB value of [EMISSIVE_COLOR], and be independant of the RGB value of [EM_BLOCK_COLOR].
#define EM_MASK_MATRIX list(0,0,0,1/3, 0,0,0,1/3, 0,0,0,1/3, 0,0,0,0, 1,1,1,0)
/// A globaly cached version of [EM_MASK_MATRIX] for quick access.
+5
View File
@@ -134,10 +134,12 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
update_sightflags(mymob, mymob.sight, NONE)
/datum/hud/proc/client_refresh(datum/source)
SIGNAL_HANDLER
RegisterSignal(mymob.client, COMSIG_CLIENT_SET_EYE, PROC_REF(on_eye_change))
on_eye_change(null, null, mymob.client.eye)
/datum/hud/proc/clear_client(datum/source)
SIGNAL_HANDLER
if(mymob.canon_client)
UnregisterSignal(mymob.canon_client, COMSIG_CLIENT_SET_EYE)
@@ -148,6 +150,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/datum/hud/proc/on_eye_change(datum/source, atom/old_eye, atom/new_eye)
SIGNAL_HANDLER
SEND_SIGNAL(src, COMSIG_HUD_EYE_CHANGED, old_eye, new_eye)
if(old_eye)
UnregisterSignal(old_eye, COMSIG_MOVABLE_Z_CHANGED)
if(new_eye)
@@ -158,6 +162,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
eye_z_changed(new_eye)
/datum/hud/proc/update_sightflags(datum/source, new_sight, old_sight)
SIGNAL_HANDLER
// If neither the old and new flags can see turfs but not objects, don't transform the turfs
// This is to ensure parallax works when you can't see holder objects
if(should_sight_scale(new_sight) == should_sight_scale(old_sight))
+19 -6
View File
@@ -550,18 +550,31 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/plane_master)
documentation = "Holds camera static images. Usually only visible to people who can well, see static.\
<br>We use images rather then vis contents because they're lighter on maptick, and maptick sucks butt."
plane = CAMERA_STATIC_PLANE
start_hidden = TRUE
/atom/movable/screen/plane_master/camera_static/show_to(mob/mymob)
// If we aren't an AI, we have no need for this plane master (most of the time, ai eyes are weird and annoying)
if(force_hidden && isAI(mymob))
unhide_plane(mymob)
. = ..()
if(!.)
return
if(isAI(mymob))
var/datum/hud/our_hud = home.our_hud
if(isnull(our_hud))
return
return FALSE
// We'll hide the slate if we're not seeing through a camera eye
// This can call on a cycle cause we don't clear in hide_from
// Yes this is the best way of hooking into the hud, I hate myself too
RegisterSignal(our_hud, COMSIG_HUD_EYE_CHANGED, PROC_REF(eye_changed), override = TRUE)
eye_changed(our_hud, null, our_hud.mymob?.client?.eye)
/atom/movable/screen/plane_master/camera_static/proc/eye_changed(datum/hud/source, atom/old_eye, atom/new_eye)
SIGNAL_HANDLER
if(!isaicamera(new_eye))
if(!force_hidden)
hide_plane(source.mymob)
return
if(force_hidden)
unhide_plane(source.mymob)
/atom/movable/screen/plane_master/high_game
name = "High Game"
+6 -5
View File
@@ -1,7 +1,10 @@
/mob/camera/ai_eye/remote/holo/setLoc(turf/destination, force_update = FALSE)
. = ..()
// If we're moving outside the space of our projector, then just... don't
var/obj/machinery/holopad/H = origin
H?.move_hologram(eye_user, loc)
if(!H?.move_hologram(eye_user, destination))
sprint = initial(sprint) // Reset sprint so it doesn't balloon in our calling proc
return
return ..()
/obj/machinery/holopad/remove_eye_control(mob/living/user)
if(user.client)
@@ -63,9 +66,7 @@
//cleans up ALL references :)
/datum/holocall/Destroy()
QDEL_NULL(hangup)
if(!QDELETED(eye))
QDEL_NULL(eye)
QDEL_NULL(eye)
if(connected_holopad && !QDELETED(hologram))
hologram = null
+36
View File
@@ -2098,3 +2098,39 @@
if(caller && (caller.pass_flags & pass_flags_self))
return TRUE
. = !density
/// Makes this atom look like a "hologram"
/// So transparent, blue, with a scanline and an emissive glow
/// This is acomplished using a combination of filters and render steps/overlays
/// The degree of the opacity is optional, based off the opacity arg (0 -> 1)
/atom/proc/makeHologram(opacity = 0.5)
// First, we'll make things blue (roughly) and sorta transparent
add_filter("HOLO: Color and Transparent", 1, color_matrix_filter(rgb(125,180,225, opacity * 255)))
// Now we're gonna do a scanline effect
// Gonna take this atom and give it a render target, then use it as a source for a filter
// (We use an atom because it seems as if setting render_target on an MA is just invalid. I hate this engine)
var/static/atom/movable/scanline
if(!scanline)
scanline = new(null)
scanline.icon = 'icons/effects/effects.dmi'
scanline.icon_state = "scanline"
// * so it doesn't render
scanline.render_target = "*HoloScanline"
// Now we add it as a filter, and overlay the appearance so the render source is always around
add_filter("HOLO: Scanline", 2, alpha_mask_filter(render_source = scanline.render_target))
add_overlay(scanline)
// Annd let's make the sucker emissive, so it glows in the dark
if(!render_target)
var/static/uid = 0
render_target = "HOLOGRAM [uid]"
uid++
// I'm using static here to reduce the overhead, it does mean we need to do plane stuff manually tho
var/static/atom/movable/render_step/emissive/glow = new(null)
glow.render_source = render_target
SET_PLANE_EXPLICIT(glow, initial(glow.plane), src)
// We're creating a render step that copies ourselves, and draws it to the emissive plane
// Then we overlay it, and release "ownership" back to this proc, since we get to keep the appearance it generates
// We can't just use an MA from the start cause render_source setting starts going fuckey REALLY quick
var/mutable_appearance/glow_appearance = new(glow)
add_overlay(glow_appearance)
LAZYADD(update_overlays_on_z, glow_appearance)
@@ -92,9 +92,6 @@
user.remote_control = null
current_user = null
user.unset_machine()
for(var/atom/movable/screen/plane_master/plane_static in user.hud_used?.get_true_plane_masters(CAMERA_STATIC_PLANE))
plane_static.hide_plane(user)
playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
/obj/machinery/computer/camera_advanced/check_eye(mob/user)
@@ -179,9 +176,6 @@
eyeobj.setLoc(eyeobj.loc)
if(should_supress_view_changes)
user.client.view_size.supress()
// Who passes control like this god I hate static code
for(var/atom/movable/screen/plane_master/plane_static in user.hud_used?.get_true_plane_masters(CAMERA_STATIC_PLANE))
plane_static.unhide_plane(user)
/mob/camera/ai_eye/remote
name = "Inactive Camera Eye"
+66 -46
View File
@@ -45,6 +45,9 @@ Possible to do for anyone motivated enough:
max_integrity = 300
armor_type = /datum/armor/machinery_holopad
circuit = /obj/item/circuitboard/machine/holopad
// Blue, dim light
light_power = 0.8
light_color = LIGHT_COLOR_BLUE
/// associative lazylist of the form: list(mob calling us = hologram representing that mob).
/// this is only populated for holopads answering calls from another holopad
var/list/masters
@@ -527,31 +530,29 @@ Possible to do for anyone motivated enough:
to_chat(user, "[span_danger("ERROR:")] \black Image feed in progress.")
return
var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location.
// What to pull our appearance out of
var/obj/effect/overlay/holo_pad_hologram/hologram = new(loc)//Spawn a blank effect at the location.
var/atom/work_off = AI?.hologram_appearance || user
hologram.icon = work_off.icon
hologram.icon_state = work_off.icon_state
hologram.copy_overlays(work_off, TRUE)
hologram.makeHologram()
if(AI)
Hologram.icon = AI.holo_icon
AI.eyeobj.setLoc(get_turf(src)) //ensure the AI camera moves to the holopad
else //make it like real life
Hologram.icon = user.icon
Hologram.icon_state = user.icon_state
Hologram.copy_overlays(user, TRUE)
//codersprite some holo effects here
Hologram.alpha = 100
Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY)
Hologram.Impersonation = user
hologram.Impersonation = user
hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it.
hologram.layer = FLY_LAYER //Above all the other objects/mobs. Or the vast majority of them.
SET_PLANE_EXPLICIT(hologram, ABOVE_GAME_PLANE, src)
hologram.set_anchored(TRUE)//So space wind cannot drag it.
hologram.name = "[user.name] (Hologram)"//If someone decides to right click.
Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it.
Hologram.layer = FLY_LAYER //Above all the other objects/mobs. Or the vast majority of them.
SET_PLANE_EXPLICIT(Hologram, ABOVE_GAME_PLANE, src)
Hologram.set_anchored(TRUE)//So space wind cannot drag it.
Hologram.name = "[user.name] (Hologram)"//If someone decides to right click.
Hologram.set_light(2) //hologram lighting
move_hologram()
set_holo(user, Hologram)
set_holo(user, hologram)
visible_message(span_notice("A holographic image of [user] flickers to life before your eyes!"))
return Hologram
return hologram
else
to_chat(user, "[span_danger("ERROR:")] Unable to project hologram.")
@@ -672,20 +673,20 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
return FALSE
/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf)
if(LAZYLEN(masters) && masters[user])
var/obj/effect/overlay/holo_pad_hologram/holo = masters[user]
var/transfered = FALSE
if(!validate_location(new_turf))
if(!transfer_to_nearby_pad(new_turf,user))
clear_holo(user)
return FALSE
else
transfered = TRUE
//All is good.
holo.abstract_move(new_turf)
SET_PLANE(holo, ABOVE_GAME_PLANE, new_turf)
if(!transfered)
update_holoray(user,new_turf)
if(!LAZYLEN(masters) || !masters[user])
return TRUE
var/obj/effect/overlay/holo_pad_hologram/holo = masters[user]
var/transfered = FALSE
if(!validate_location(new_turf))
if(!transfer_to_nearby_pad(new_turf,user))
return FALSE
else
transfered = TRUE
//All is good.
holo.abstract_move(new_turf)
SET_PLANE(holo, ABOVE_GAME_PLANE, new_turf)
if(!transfered)
update_holoray(user,new_turf)
return TRUE
@@ -715,21 +716,19 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
// RECORDED MESSAGES
/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record)
var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location.
Hologram.add_overlay(record.caller_image)
Hologram.alpha = 170
Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY)
Hologram.dir = SOUTH //for now
var/datum/language_holder/holder = Hologram.get_language_holder()
var/obj/effect/overlay/holo_pad_hologram/hologram = new(loc)//Spawn a blank effect at the location.
hologram.add_overlay(record.caller_image)
hologram.makeHologram()
var/datum/language_holder/holder = hologram.get_language_holder()
holder.selected_language = record.language
Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it.
Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them.
SET_PLANE_EXPLICIT(Hologram, ABOVE_GAME_PLANE, src)
Hologram.set_anchored(TRUE)//So space wind cannot drag it.
Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click.
Hologram.set_light(2) //hologram lighting
hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it.
hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them.
SET_PLANE_EXPLICIT(hologram, ABOVE_GAME_PLANE, src)
hologram.set_anchored(TRUE)//So space wind cannot drag it.
hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click.
visible_message(span_notice("A holographic image of [record.caller_name] flickers to life before your eyes!"))
return Hologram
return hologram
/obj/machinery/holopad/proc/replay_start()
if(!replay_mode)
@@ -827,6 +826,8 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
QDEL_NULL(disk.record)
/obj/effect/overlay/holo_pad_hologram
// Adds KEEP_TOGETHER to ensure we render overlays right
appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE|KEEP_TOGETHER
initial_language_holder = /datum/language_holder/universal
var/mob/living/Impersonation
var/datum/holocall/HC
@@ -858,6 +859,25 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
pixel_x = -32
pixel_y = -32
alpha = 100
var/atom/movable/render_step/emissive/glow
/obj/effect/overlay/holoray/Initialize(mapload)
. = ..()
if(!render_target)
var/static/uid = 0
render_target = "holoray#[uid]"
uid++
// Let's GLOW BROTHER! (Doing it like this is the most robust option compared to duped overlays)
glow = new(src, render_target)
// We need to counteract the pixel offset to ensure we don't double offset (I hate byond)
glow.pixel_x = 32
glow.pixel_y = 32
add_overlay(glow)
LAZYADD(update_overlays_on_z, glow)
/obj/effect/overlay/holoray/Destroy(force)
. = ..()
QDEL_NULL(glow)
#undef CAN_HEAR_ACTIVE_HOLOCALLS
#undef CAN_HEAR_ALL_FLAGS
+3 -7
View File
@@ -163,13 +163,8 @@
GLOBAL_VAR_INIT(icon_holographic_wall, init_holographic_wall())
GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window())
// `initial` does not work here. Neither does instantiating a wall/whatever
// and referencing that. I don't know why.
/proc/init_holographic_wall()
return getHologramIcon(
icon('icons/turf/walls/wall.dmi', "wall-0"),
opacity = 1,
)
return icon('icons/turf/walls/wall.dmi', "wall-0")
/proc/init_holographic_window()
var/icon/grille_icon = icon('icons/obj/structures.dmi', "grille")
@@ -177,7 +172,7 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window())
grille_icon.Blend(window_icon, ICON_OVERLAY)
return getHologramIcon(grille_icon)
return grille_icon
/obj/item/construction/rcd/ui_action_click(mob/user, actiontype)
if (!COOLDOWN_FINISHED(src, destructive_scan_cooldown))
@@ -223,6 +218,7 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window())
var/obj/effect/rcd_hologram/hologram = new(surrounding_turf)
hologram.icon = hologram_icon
hologram.makeHologram()
animate(hologram, alpha = 0, time = fade_time, easing = CIRCULAR_EASING | EASE_IN)
/obj/effect/rcd_hologram
+16 -19
View File
@@ -36,7 +36,7 @@
var/aiRestorePowerRoutine = POWER_RESTORATION_OFF
var/requires_power = POWER_REQ_ALL
var/can_be_carded = TRUE
var/icon/holo_icon //Default is assigned when AI is created.
var/mutable_appearance/hologram_appearance //Default is assigned when AI is created.
var/obj/controlled_equipment //A piece of equipment, to determine whether to relaymove or use the AI eye.
var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not.
radiomod = ";" //AIs will, by default, state their laws on the internal radio.
@@ -163,7 +163,7 @@
INVOKE_ASYNC(src, PROC_REF(set_core_display_icon))
holo_icon = getHologramIcon(icon('icons/mob/silicon/ai.dmi',"default"))
hologram_appearance = mutable_appearance('icons/mob/silicon/ai.dmi',"default")
spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src)
@@ -674,22 +674,17 @@
return
var/mutable_appearance/character_icon = personnel_list[input]
if(character_icon)
qdel(holo_icon)//Clear old icon so we're not storing it in memory.
character_icon.setDir(SOUTH)
var/icon/icon_for_holo = getFlatIcon(character_icon)
holo_icon = getHologramIcon(icon(icon_for_holo))
hologram_appearance = character_icon
if("My Character")
switch(tgui_alert(usr,"WARNING: Your AI hologram will take the appearance of your currently selected character ([usr.client.prefs?.read_preference(/datum/preference/name/real_name)]). Are you sure you want to proceed?", "Customize", list("Yes","No")))
if("Yes")
var/mob/living/carbon/human/dummy/ai_dummy = new
var/mutable_appearance/appearance = usr.client.prefs.render_new_preview_appearance(ai_dummy)
var/icon/character_icon = getHologramIcon(getFlatIcon(appearance))
if(character_icon)
qdel(holo_icon)
var/mutable_appearance/dummy_appearance = usr.client.prefs.render_new_preview_appearance(ai_dummy)
if(dummy_appearance)
qdel(ai_dummy)
holo_icon = character_icon
hologram_appearance = dummy_appearance
if("No")
return FALSE
@@ -715,16 +710,17 @@
return
if(isnull(icon_list[input]))
return
qdel(holo_icon)
var/working_state = ""
switch(input)
if("poly")
holo_icon = getHologramIcon(icon(icon_list[input],"parrot_fly"))
working_state = "parrot_fly"
if("chicken")
holo_icon = getHologramIcon(icon(icon_list[input],"chicken_brown"))
working_state = "chicken_brown"
if("spider")
holo_icon = getHologramIcon(icon(icon_list[input],"guard"))
working_state = "guard"
else
holo_icon = getHologramIcon(icon(icon_list[input], input))
working_state = input
hologram_appearance = mutable_appearance(icon_list[input], working_state)
else
var/list/icon_list = list(
"default" = 'icons/mob/silicon/ai.dmi',
@@ -739,12 +735,13 @@
return
if(isnull(icon_list[input]))
return
qdel(holo_icon)
var/working_state = ""
switch(input)
if("xeno queen")
holo_icon = getHologramIcon(icon(icon_list[input],"alienq"))
working_state = "alienq"
else
holo_icon = getHologramIcon(icon(icon_list[input], input))
working_state = input
hologram_appearance = mutable_appearance(icon_list[input], working_state)
return
/datum/action/innate/core_return
@@ -112,7 +112,9 @@
//Holopad
if(istype(ai.current, /obj/machinery/holopad))
var/obj/machinery/holopad/H = ai.current
H.move_hologram(ai, destination)
if(!H.move_hologram(ai, destination))
H.clear_holo(ai)
if(ai.camera_light_on)
ai.light_cameras()
if(ai.master_multicam)
+15 -5
View File
@@ -56,11 +56,6 @@
* This should only be used internally. If you are directly creating more of these, you're
* almost guaranteed to be doing something wrong.
*/
/**
* Render step that modfies an atom's color
* Useful for creating coherent emissive blockers out of things like glass floors by lowering alpha statically using matrixes
* Other stuff too I'm sure
*/
/atom/movable/render_step/emissive_blocker
name = "emissive blocker"
plane = EMISSIVE_PLANE
@@ -69,3 +64,18 @@
/atom/movable/render_step/emissive_blocker/Initialize(mapload, source)
. = ..()
src.color = GLOB.em_block_color
/**
* Render step that makes the passed in render source GLOW
*
* Copies an appearance vis render_target and render_source on to the emissive plane
*/
/atom/movable/render_step/emissive
name = "emissive"
plane = EMISSIVE_PLANE
appearance_flags = EMISSIVE_APPEARANCE_FLAGS|RESET_TRANSFORM
/atom/movable/render_step/emissive/Initialize(mapload, source)
. = ..()
src.color = GLOB.emissive_color