Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vision Cones #24537

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion code/__DEFINES/misc.dm
Expand Up @@ -411,4 +411,6 @@ var/global/list/ghost_others_options = list(GHOST_OTHERS_SIMPLE, GHOST_OTHERS_DE
#define TURF_DECAL_DIRT "dirt"

//Error handler defines
#define ERROR_USEFUL_LEN 2
#define ERROR_USEFUL_LEN 2

#define OPPOSITE_DIR(D) turn(D, 180)
3 changes: 3 additions & 0 deletions code/_onclick/hud/human.dm
Expand Up @@ -302,6 +302,9 @@
pull_icon.screen_loc = ui_pull_resist
static_inventory += pull_icon

mymob.fov = new /obj/screen/fov()
static_inventory += mymob.fov
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the wrong way to do this, I'm sure.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah press F12 or whatever hotkey that was and its gone


lingchemdisplay = new /obj/screen/ling/chems()
infodisplay += lingchemdisplay

Expand Down
11 changes: 10 additions & 1 deletion code/_onclick/hud/screen_objects.dm
Expand Up @@ -691,4 +691,13 @@

/obj/screen/splash/Destroy()
holder.screen -= src
return ..()
return ..()

/obj/screen/fov
icon = 'icons/mob/vision_cone.dmi'
icon_state = "combat"
name = " "
screen_loc = "1,1"
mouse_opacity = 0
layer = HUD_LAYER
plane = HUD_PLANE
9 changes: 8 additions & 1 deletion code/datums/hud.dm
Expand Up @@ -48,6 +48,7 @@ var/datum/atom_hud/huds = list( \
return
for(var/i in hud_icons)
M.client.images -= A.hud_list[i]
M.client.hidden_images -= A.hud_list[i]

/datum/atom_hud/proc/add_hud_to(mob/M)
if(!M)
Expand All @@ -66,9 +67,15 @@ var/datum/atom_hud/huds = list( \
/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
if(!M || !M.client || !A)
return
var/in_cone = FALSE
if(A in cone(M, OPPOSITE_DIR(M.dir), view(10, M)))
in_cone = TRUE
for(var/i in hud_icons)
if(A.hud_list[i])
M.client.images |= A.hud_list[i]
if(in_cone)
M.client.hidden_images |= A.hud_list[i]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you had this be an associated list it would be faster then |= (but not by much)

a better way would be to find some way to do tracking of what a client has so you can do += safely

else
M.client.images |= A.hud_list[i]

//MOB PROCS
/mob/proc/reload_huds()
Expand Down
1 change: 1 addition & 0 deletions code/modules/mob/living/emote.dm
Expand Up @@ -459,6 +459,7 @@
message = "spins around dizzily!"

/datum/emote/living/spin/run_emote(mob/user)
user.client.move_delay = (user.movement_delay() * 2) + world.time
user.spin(20, 1)
if(istype(user, /mob/living/silicon/robot))
var/mob/living/silicon/robot/R = user
Expand Down
11 changes: 11 additions & 0 deletions code/modules/mob/living/living.dm
Expand Up @@ -443,6 +443,12 @@
if (s_active && !(s_active.ClickAccessible(src, depth=STORAGE_VIEW_DEPTH) || s_active.Adjacent(src)))
s_active.close(src)

for(var/mob/M in oview(src))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this with viewers() and then you will only get mobs and can remove the type on the for and do a istypeless for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

istypeless

Would that include ghosts then? I just realized its been tracking ghosts this whole time, which seems a bit pointless

if(M.client && (src in cone(M, OPPOSITE_DIR(M.dir), view(10, M))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the way this works, you could refactor it to cache the details for cone and view() by pivoting it to src rather then m so it only needs to be done once.

I don't know of any situation for something to be in one things view but for that thing to not be in its view, view is two way link is it not?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kor this isn't what I meant, you don't need to update the entire vision cone for a mob who isn't moving - they just need to know that this mob moved and then they need to check if the currently moving mob has entered or exited their cone zone and if so they need to hide or unhide appropriately

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah that makes sense

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e the only mob that needs to actually call update_vision cone is the actually moving mob

M.update_vision_cone()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coming back to this, I wonder if it might be better to operate on the living mob list rather then do these complex view operations.
if you give it a early out for range it might work

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to be the biggest source of lag. this is 1 view operation, and then a loop of view operations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do it on the player list instead of the mob list, would be significantly smaller

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KorPhaeron but then you can see animals and that's dumb, don't half-ass it.


update_vision_cone()

/mob/living/movement_delay(ignorewalk = 0)
. = ..()
if(isopenturf(loc) && !is_flying())
Expand Down Expand Up @@ -915,3 +921,8 @@
"[C] topples over [src]!", \
"[C] leaps out of [src]'s way!")]</span>")
C.Weaken(2)


/mob/living/setDir(newdir)
..()
update_vision_cone()
1 change: 1 addition & 0 deletions code/modules/mob/mob.dm
Expand Up @@ -665,6 +665,7 @@ var/next_mob_id = 0
else
if(layer == LYING_MOB_LAYER)
layer = initial(layer)
update_vision_cone()
update_transform()
update_action_buttons_icon(status_only=TRUE)
if(isliving(src))
Expand Down
91 changes: 91 additions & 0 deletions code/modules/mob/vision_cone.dm
@@ -0,0 +1,91 @@
/mob
var/obj/screen/fov = null//The screen object because I can't figure out how the hell TG does their screen objects so I'm just using legacy code.

client/
var/list/hidden_atoms = list()
var/list/hidden_mobs = list()
var/list/hidden_images = list()



//Procs
/atom/proc/InCone(atom/center = usr, dir = NORTH)
if(get_dist(center, src) == 0 || src == center) return 0
var/d = get_dir(center, src)
if(!d || d == dir) return 1
if(dir & (dir-1))
return (d & ~dir) ? 0 : 1
if(!(d & dir)) return 0
var/dx = abs(x - center.x)
var/dy = abs(y - center.y)
if(dx == dy) return 1
if(dy > dx)
return (dir & (NORTH|SOUTH)) ? 1 : 0
return (dir & (EAST|WEST)) ? 1 : 0

/mob/dead/InCone(mob/center = usr, dir = NORTH)//So ghosts aren't calculated.
return

/proc/cone(atom/center = usr, dir = NORTH, list/list = oview(center))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove annoying X in Y checks by making this:

/proc/cone(atom/center = usr, dir = NORTH, list/L = oview(center))
    . = list()
    for(var/atom/A in L)
        if(A.InCone(center, dir))
            .[A] = A

and then just doing

var/list/cone = cone(...)
if(cone[A])
    ...

for(var/atom/A in list)
if(!A.InCone(center, dir))
list -= A
return list

/mob/proc/update_vision_cone()
return

/mob/living/update_vision_cone()
if(src.client)
var/image/I = null
for(I in src.client.hidden_atoms)
I.override = 0
client.images -= I
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var/list/imagecopy = client.images.Copy()

then do all operations on the copy of the list, then assign client.images to the copy.

this saves the overhead of byond parsing the image list so it does it only once.

qdel(I)
for(var/hidden_hud in client.hidden_images)
client.images += hidden_hud
client.hidden_images -= hidden_hud
rest_cone_act()
src.client.hidden_atoms = list()
src.client.hidden_mobs = list()
client.hidden_images = list()
src.fov.dir = src.dir
if(fov.alpha != 0)
var/mob/living/M
for(M in cone(src, OPPOSITE_DIR(src.dir), view(10, src)))
I = image("split", M)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be cached to avoid building it so often I think

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can just be a global var

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't be global, has to have it's loc set to M (can't just be an overlay because then everyone would see it, instead of just the person who's cone decided to hide M), meaning it must be unique-per-mob, this means you'll always need N images, which is still better than infinite creation.

Hiding/Unhiding can then be done as:
person.hidden.override = TRUE/person.hidden.override = FALSE (since this will only apply it for people who have it in their client.images list)

I.override = 1
src.client.images += I
src.client.hidden_atoms += I
src.client.hidden_mobs += M
if(src.pulling == M)//If we're pulling them we don't want them to be invisible, too hard to play like that.
I.override = 0
for(var/image/HUD in client.images)
if(HUD.icon != 'icons/mob/hud.dmi')
continue
for(var/mob/living/M in client.hidden_mobs)
if(HUD.loc == M)
client.hidden_images += HUD
client.images -= HUD
break
else
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing return, remove it please.


/mob/proc/rest_cone_act()//For showing and hiding the cone when you rest or lie down.
if(resting || lying)
hide_cone()
else
show_cone()

//Making these generic procs so you can call them anywhere.
/mob/proc/show_cone()
if(src.fov)
src.fov.alpha = 255

/mob/proc/hide_cone()
if(src.fov)
src.fov.alpha = 0




Binary file added icons/mob/vision_cone.dmi
Binary file not shown.
1 change: 1 addition & 0 deletions tgstation.dme
Expand Up @@ -1395,6 +1395,7 @@
#include "code\modules\mob\status_procs.dm"
#include "code\modules\mob\transform_procs.dm"
#include "code\modules\mob\update_icons.dm"
#include "code\modules\mob\vision_cone.dm"
#include "code\modules\mob\camera\camera.dm"
#include "code\modules\mob\dead\death.dm"
#include "code\modules\mob\dead\observer\login.dm"
Expand Down