Visibility Groups

by Forum_account
A library to manage image objects.
ID:119414
 
It's a very simple library that manages image objects for you. You just assign objects to visibility groups and specify which groups a mob can see, and the image objects are automatically added and removed from the client's image list. For example:

mob
Login()
..()
vis = new(src)

proc
die()
// when a player dies they become a ghost and can see ghosts
vis.is_a(GHOST)
vis.can_see(GHOST)

respawn()
// when the player respawns they are no longer a ghost and cannot see ghosts
vis.is_not_a(GHOST)
vis.cant_see(GHOST)


The vis var is the object that manages visibility groups. The four procs that are shown in that example are how you use it. The argument, GHOST, is just a text string that's the name of the visibility group. You can pass the procs any number of arguments to add or remove multiple groups at a time.

The library also defines a "can_see()" proc for mobs. The proc is passed an atom and returns 1 if the mob can see that atom (based on what visibility groups the mob can see and what groups the atom is in) and 0 if it cannot. You can change this or override it to change how the library works.

By default, you can see an atom if it belongs to at least one visibility group that you can see. For example, if a mob is in the "ghost" and "lizard people" groups, you can see the mob if you can view either of those groups. You don't have to be able to view both. But, if you override the mob's can_see proc in your code, you can change it to require all groups.
But can it animate correctly?
It uses image objects, so it's limited by what image objects can't do. I think they'll show the direction and movement animation of the object they're attached do, but I don't think flick() works on them.
Yay! :D
Amazing. Good job.
This is awesome, very very well done! (:
I noticed a problem:

I ran the demo files, I clicked "Hide Gray mobs" (Dunno why..)

Then I clicked "show gray mobs" and I could see the gray guys.

Did the same thing as above with Red's verbs, then I clicked "Hide Spies" and text output said they're hidden, but nothing happened, then I click "Show Spies" and once again, text yet nothing happened.

I closed the game and ran it again (manual reboot?) I thought, "Perhaps it was a "hide all spies" problem.", so I first clicked "hide all spies", they were invisible like they all are by default, then I clicked "show all spies" and they all appeared.

EDIT: I ran through the code, very cool library!
Truseeker wrote:
I ran the demo files, I clicked "Hide Gray mobs" (Dunno why..)
Then I clicked "show gray mobs" and I could see the gray guys.

That's what should happen. The vis object keeps track of what groups you can see. When you click a "hide" verb it removes a group from the list, clicking a "show" verb adds the group to that list. By default you can't see any groups so trying to hide gray mobs does nothing ("gray" was not in the list of groups you can see, so removing it does nothing). When you click the "show" verb the group is added to the list of groups you can see, making them visible.

The library was made to address this feature request, which asks for the ability to "selectively hide" objects, but the library (by default) lets you selectively show objects to players. By default you can't see any groups and you have specify which ones you can see. You can sort of use the library the other way - so that you can see all mobs and tell it who you can't see:

mob
New()
..()
vis = new(src)
vis.can_see(RED, GRAY, SPY)


Then later on in the code call vis.cant_see() to selectively hide groups.
Forum_account wrote:
It uses image objects, so it's limited by what image objects can't do. I think they'll show the direction and movement animation of the object they're attached do, but I don't think flick() works on them.

I'm not sure if there's a way to easily find how many frames an icon state has, if there is, it would be nice to make an alternative flick proc using single loop icon states.
I think it's possible to find how many frames an animated icon state has, but I'm pretty sure the problem is that you can't find out the duration of each frame. It'd be nice to be able to get this information (for this and other uses) but building up the set of individual frames and using a loop to play them as an animation would not be terribly efficient - it'd just be nice if flick() worked on image objects.
I guess this would work for some people in a limited way until this is added.
You wouldn't be looping each frame to create a flick, you'd just change the icon_state, sleep the duration of the animation, then change the icon state to it's previous state. When you set the animation to loop once in a .dmi file it plays the animation start to finish when you change the icon state, it just sticks the last frame, which is why you'd need to switch the icon state back to it's previous state.

[Edit:]
Obviously this can be done manually, but if it was possible to retrieve the time the animation took, it would be helpful to have in this lib.
Megablaze wrote:
Obviously this can be done manually, but if it was possible to retrieve the time the animation took, it would be helpful to have in this lib.

I agree, though I'd probably make that it's own lib or add it to Handy Stuff (which has a way to flick overlays).

SuperAntx wrote:
I guess this would work for some people in a limited way until this is added.

I wanted to provide the feature in case it's never added natively. I also wanted to show how simple it is to handle yourself. I'd also like to see the staff ignore that feature request and work on improving mouse support.

There are lots of ways to complicate this feature that the feature request didn't really address. For instance, if a mob is in the "hidden" and "dragon" visibility groups, to see that mob is it sufficient to be able to view either hidden mobs or dragons? Or do you need to be able to see both? Depending on how things are done (ex: if visibility is partially enforced on the client) this could be quite a switch.

There are many ways to extend the feature that start to be highly game-specific. You might want invisible mobs to not block movement. You might want mobs to be shown with different icons based on what visibility groups you can see (ex: in the hidden dragon case, being able to see hidden mobs but not dragons would make the hidden dragon be visible, but only as a generic mob icon).
The fact they added pixel movement should be a strong indicator they're taking the votes seriously. Hopefully that feature will be the next big thing they add.

For instance, if a mob is in the "hidden" and "dragon" visibility groups, to see that mob is it sufficient to be able to view either hidden mobs or dragons?

Yes, that's the idea. I don't see what's so confusing about it.

You might want invisible mobs to not block movement.

Anyone using just "density" for their collision probably wont be in a position to take full advantage of the visibility groups in the first place. With the core features in place it should be a trivial matter of deciding what bumps into what, something which should be left to developers to figure out for their own specific needs.

You might want mobs to be shown with different icons based on
what visibility groups you can see

This is actually a pretty neat idea I hadn't considered. Though, it does seem like an entirely different feature by itself. In the mean time it would be possible to pull it off by having "actor" objects animate the different sprites in the same location, having them use the visibility groups. Really, it's outside of the scope of the feature request.
SuperAntx wrote:
Yes, that's the idea. I don't see what's so confusing about it.

The problem is that people will need to customize how this feature works because there are multiple ways they might want it to work. Without customization it's not that useful (keep in mind that it's not that useful to begin with - it's something that maybe 1% of games would use). With customization it can easily become infeasible.

If BYOND defines a mob.CanSee(atom/a) proc that is used to determine if the mob can see an object, the developer can override it to control the behavior. There could be some built-in vars for visibility groups and some default behavior and you could easily override it to make the change I mentioned (requiring sight of all groups, not just one group to see an object). The question is, when does BYOND evaluate this function? The server would need to know when the function's value changes, but because the value could be based on any variable (it could even be random) it'd have to call the CanSee() proc very often for every object you can see.

Also, if any visibility checks are handled by the client, this would force them all to be handled on the server. Handling custom logic on the client would be nice but is a lot to add for this one feature.
This is also handy for doing things like roofs on buildings. You give each roof its own visibility group. When you enter the building you call vis.cant_see(roof_group) to disable sight of its roof. When you exit the building you call vis.can_see(roof_group) to enable sight of its roof. The code ends up being very simple.

I'll update the library later to include this as an additional demo.
I didn't get around to it as quickly as I had planned, but I just posted the update to add a new demo showing how to create roofs using this library. It's very simple, here's all you have to do:

area
roof
layer = MOB_LAYER + 1

var
group_name = ""

// create the vis object for each roof instance and
// make it a member of its specified visiblity group
New()
..()
vis = new(src)
vis.is_a(group_name)

// when you enter a roofed area you can no longer see the roof
Entered(mob/m)
if(m.vis)
m.vis.cant_see(group_name)

// when you exit a roofed area you can see the roof again
Exited(mob/m)
if(m.vis)
m.vis.can_see(group_name)

mob
Login()
vis = new(src)

// initially you can see all roofs
for(var/area/roof/r in world)
vis.can_see(r.group_name)

..()


Each roof type is its own visibility group. When you're outside of a building you can see that roof's visibility group, when you're inside you can't see it.