Mar 1 2015, 5:16 pm (Edited on Mar 1 2015, 11:25 pm) Sorry I spent so long hacking this snippet together. Some things went wrong and I discovered a few BYOND bugs along the way. Alright, so let's get started. First, let's talk about what this system is going to do. This system is going to allow you to paint objects on the map as a means of defining roof tiles. On initialization, if there are multiple of these objects in a single tile, they will merge into a single roof object by adding their appearances to the root roof object's overlays list. Objects that have been merged with the root are then moved to null location, where they will be garbage collected. After the map has been initialized, world/New() is called. We loop through all of the tiles that were flagged as having roof objects and use a clever trick to generate a unique id based on the roof object's overlays list. If the current area object that the roof is located within doesn't already have a copy of that unique overlays list, we subdivide it, creating a new subarea. If the area already has a subarea with the id matching the unique overlays list of the roof object, simply add the current turf of the roof object to that subarea. The subarea's overlays list will be set to the roof object's overlays list, then the roof object will be sent to a null location where it will be garbage collected. Areas and subareas will require some reworking of the default area behavior. We need to make it so that multiple different area instances will behave as though they were a single area instance. We do this by creating subdivision behavior that links the instances by a root area. Subareas will act as proxies for the root area, and call the root area's Enter()/Exit()/Entered()/Exited() procs instead of their own. Further, Entered()/Exited() will return zero by default if the referenced movable is moving between areas that are subareas of the same root area, or between the root area and one of its subareas. Lastly, when a player enters a root area, it will use another clever trick to hide the area with a nice little fading animation. Clever trick #1 explained: The first clever trick I mentioned requires a little explanation. First of all, I talk about "appearance abuse" quite a lot in my rantings. Understanding what appearances are and how they work is key to this particular trick. In BYOND, all atoms and images have a thing called an appearance. Appearances are unique. Meaning no two appearances can be the same. If two objects are given the same visual properties (icon, icon_state, color, alpha, pixel_x, pixel_y, pixel_z, transform, and a few others), the two objects will actually share a reference to the same appearance object on the server. The only place you can access appearance information is actually in overlays and underlays lists. Each entry in an overlay/underlay list is actually an appearance. This is why you can add types, strings, objects and icons to overlays: It's because they actually convert all of the operands of the add operator to appearances. This is also why you sometimes get "stuck" overlays/underlays, because when you change the visual appearance of an object, the appearance reference of the object changes and subtracting that object doesn't remove the old appearance, only the new. This particular quirk is actually quite useful as I showcase in my "appearance abuse" snippets. Overlay/underlay lists are actually unique as well. Identical overlays/underlays lists share the same ID as each other. mob/verb/test_appearances() var/obj/o = new() o.icon = 'someicon.dmi' src.overlays += o world << "Appearance 1: \ref[src.overlays[src.overlays.len]]" world << "Overlays 1: \ref[src.overlays]" o.icon = 'someicon2.dmi' src.overlays += o world << "Appearance 2: \ref[src.overlays[src.overlays.len]]" world << "Overlays 2: \ref[src.overlays]" overlays.Cut(src.overlays.len-1,0)  As you can see in the snippet, each time you add something to the overlays list, it it changes reference provided it has more than a single user. You can also see in this snippet, that each time you change the object's appearance, you wind up with a new appearance reference. This is the crux of the trick that we're going to be using. We can generate only as many unique subareas as needed based on the reference id of the overlays list. Clever Trick #2 explained: The second clever trick allows us to hide objects using images. There exists a variable for images called "override". Override makes the image hide the default appearance of the object the image is attached to. This effectively makes a "negative" image possible, in that you can add a blank or otherwise invisible image to an object and show it on a per-player basis to hide an atom from a player. This is the trick that gets us around the common approach of showing all roof areas in the world to the player on login. Instead, we just give the areas default appearances that are always visible, and then when the player enters a building, we hide only that building's roofing areas from the player using an override image. Breaking areas: Alright, let's get started. First, we need to modify areas a bit to suit our purposes. We need to override the Entry/Exit functions and set up behavior that makes a subdivided area act as though it were a single large area. Beware, this breaks normal area behavior. This breaks two things: 1) Checking if an area is the same as another may result in inconsistent behavior if you want to consider subareas as the same as their root or sibling areas. 2) Getting the contents of an area will not return everything in subareas. We're going to add two functions that will fix these two problems. isSame(area/a), and getContents(). Use these two functions instead of the normal way you'd go about things if you want to use this library. area var area/root_area list/subareas proc //calls subdivide on the root area. //creates a new child area and links it with this one as the root Subdivide() //if there is a root area set, we need to perform the root's subdivide operation. if(root_area) return root_area.Subdivide() else //create a new area instance of the same type as this area var/area/a = new src.type() //set its root to this area a.root_area = src //if we don't already have a subarea list, create it and add the new area, otherwise, just add the new area if(!subareas) subareas = list(a) else subareas += a return a //returns the contents of both the root area and all of the subareas. getContents() if(root_area) return root_area.getContents() var/list/l = src.contents.Copy() if(subareas) for(var/area/a in subareas) l.Add(a.contents) return l //returns 1 if the supplied area is a child or sibling subarea to this one, or is src //returns 0 if the supplied area is not a matching area. isSame(area/a) if(a.type!=src.type) return 0 if(a==src) return 1 if(a.root_area==src||src.root_area==a) return 1 return 0 Enter(atom/movable/O, atom/oldloc) //only call Enter on the root area if(root_area) return root_area.Enter(O,oldloc) return ..() Exit(atom/movable/O, atom/newloc) //only call exit onthe root area if(root_area) return root_area.Enter(O,newloc) return ..() Entered(atom/movable/O, atom/oldloc) //only call entered on the root area if(root_area) return root_area.Entered(O,oldloc) //this will only be run if this area is the root area if(oldloc) //if the movable is moving here from elsewhere //get the old turf the player was associated with var/turf/t = locate(oldloc.x,oldloc.y,oldloc.z) //if the turf exists if(t && isSame(t.loc)) //if the old area's root area is this object, don't trigger Entered() behavior. //return 0 even though it's not necessary. We can use this for expanded behavior later. return 0 //provided the movable is coming from an area that's not a subdivision of this one: return ..() Exited(atom/movable/O, atom/newloc) //only call exited on the root area if(root_area) return root_area.Exited(O,newloc) //this will only be run if this area is the root area if(newloc) //if the movable is leaving to another location //get the new turf the player will be moving toward var/turf/t = locate(newloc.x,newloc.y,newloc.z) //if the turf exists if(t && isSame(t.loc)) //if the new area's root area is this object, don't trigger Exited() behavior. //return 0 even though it's not necessary. We can use this for expanded behavior later. return 0 //provided the movable is going to an area that's not a subdivision of this one: return ..()  The roof object: The roof object should be pretty simple to set up. All it needs to do is combine itself with earlier roof objects. We're going to store the flagged turfs in a temporary global list called __building_rooves, which will be processed during world/New(). #define ROOF_LAYER 7var initialized = FALSE //these are temporary lists. They should be null after world initialization list/__building_rooves = list()roof parent_type = /obj layer = ROOF_LAYER New() //roof objects should only be used on the map if(isturf(src.loc)) //roof objects should not be created at runtime if(!initialized) //get the base roof object from the building list var/roof/base_roof = __building_rooves[src.loc] if(base_roof) //if the base roof object exists, add this roof's appearance to its overlays and set location to null, triggering garbage collection base_roof.overlays += src src.loc = null else //if the base roof object doesn't exist, add this object's appearance to its own overlays then store it in the building list by location overlays += src __building_rooves[src.loc] = src else //if it's after the map has been initialized, just set this object to location null to trigger garbage collection src.loc = null else //if we're not in a turf, set our loc to null, hopefully triggering garbage collection. src.loc = null  The construction loop: Now that we've got roof objects implemented, let's work in the construction loop. What this will do, is loop over the __building_rooves list and subdivide the areas that roof objects are sitting in, creating only the number of area subdivisions necessary. We will use __unique_rooves as a global list storage to keep track of unique area/roof combinations temporarily. At the end of the construction loop, we will clear both of these global lists, freeing up their memory. world New() ..() //construct the rooves after the map loads construct_rooves() //set initialized to true to indicate that the map has already finished loading initialized = TRUEvar list/__unique_rooves = list()proc construct_rooves() var/roof/roof var/area/area var/area/subarea var/refid var/list/subtypes //loop through all the objects in the temporary building rooves list for(var/turf/turf in __building_rooves) //get the roof object reference from the base turf roof = __building_rooves[turf] //get the current area the roof object is in area = turf.loc //get the overlay list reference of the roof object refid = "\ref[roof.overlays]" //the unique rooves list stores a temporary association of areas with their particular unique roof overlays subtypes = __unique_rooves[area] //if this area doesn't have a subdivision yet if(!subtypes) //create the subdivision list and fill it with the current unique overlay reference subarea = area.Subdivide() subarea.overlays = roof.overlays //set up the storage list subtypes = list() subtypes[refid] = subarea __unique_rooves[area] = subtypes else //otherwise, make certain that this unique overlay list isn't in use yet. subarea = subtypes[refid] if(!subarea) //if it's not, we create a new area subdivision subarea = area.Subdivide() subarea.overlays = roof.overlays subtypes[refid] = subarea subarea.contents += turf //remember to trigger garbage collection for all roof objects, removing them from the world roof.loc = null //empty the temporary lists as they are no longer needed. __unique_rooves = null __building_rooves = null  Pretty animations: Okay, now that we've got all the structure in place, we need to actually do the hiding of the roof objects. Let's create a datum that will perform the animation for us. The reason we want a datum to do the handling for us, is because I don't like polluting the mob or client with lots of variables. This structure is much easier to read, and since our animation can be interrupted part-way, we need to store a few bits of information about the fade animation, like when it started, so we can calculate the alpha value to reverse the fade from. This will result in really smooth looking fade animations. #define ROOF_FADE_TIME 10 //how long the fade animation should takeproc //round to the next whole number to the right ceil(num) . = round(num) if(.=world.tick_lag) //loop through the images and perform the fade var/stime = world.time+world.tick_lag fade_time = stime for(var/image/j in images) animate(j) j.alpha = startalpha spawn(world.tick_lag) for(var/image/j in images) animate(j,alpha=255,time=duration) //make sure the fade animation is done spawn(ceil(duration)) //if the fade animation finished, remove all the images if(fade_time==stime) owner.images.Remove(images) owner.hide_rooves.Remove(area) //set the images' locs to null to make sure they get garbage collected for(var/image/j in images) j.loc = null else //otherwise, we just straight up remove all of the images owner.images.Remove(images) owner.hide_rooves.Remove(area) //set the images' locs to null to make sure they get garbage collected for(var/image/j in images) j.loc = null New(Area,Owner) area = Area owner = Owner  Note that the above code snippet got a little ugly because I had to work around a few BYOND bugs while I was working. I'd prefer not to iterate through the lists more than once, but because of the way that animations are sent to the client, animations that are sent on the same frame as object creation or appearance changes related to the animation cause visual flicker. Next, all we have to do is modify /area a bit and we're home free: area var area/root_area list/subareas proc HideRoof(client/client) if(subareas) //create a new hider object if one doesn't already exist for this area on the client var/roof_hider/hider = client.hide_rooves[src] if(!hider) hider = new(src,client) client.hide_rooves[src] = hider //call the hide routine hider.Hide() ShowRoof(client/client) if(subareas) //get the existing hider object var/roof_hider/hider = client.hide_rooves[src] if(hider) //call the show routine hider.Show() Entered(atom/movable/O, atom/oldloc) if(root_area) return root_area.Entered(O,oldloc) //this will only be run if this area is the root area if(oldloc) //if the movable is moving here from elsewhere //get the old turf the player was associated with var/turf/t = locate(oldloc.x,oldloc.y,oldloc.z) //if the turf exists if(t && isSame(t.loc)) //if the old area's root area is this object, don't trigger Entered() behavior. //return 0 even though it's not necessary. We can use this for expanded behavior later. return 0 //provided the movable is coming from an area that's not a subdivision of this one: if(istype(O,/mob)) var/mob/m = O if(m.client) HideRoof(m.client) return ..() Exited(atom/movable/O, atom/newloc) if(root_area) return root_area.Exited(O,newloc) //this will only be run if this area is the root area if(newloc) //if the movable is leaving to another location //get the new turf the player will be moving toward var/turf/t = locate(newloc.x,newloc.y,newloc.z) //if the turf exists if(t && isSame(t.loc)) //if the new area's root area is this object, don't trigger Exited() behavior. //return 0 even though it's not necessary. We can use this for expanded behavior later. return 0 //provided the movable is going to an area that's not a subdivision of this one: if(istype(O,/mob)) var/mob/m = O if(m.client) ShowRoof(m.client) return ..()  There you have it. That's the entire library. You can now go into the map editor and start plopping down your roof objects on top of buildings. Make sure you create a new instance of area for each building that you want to have separate rooves. I'll be posting a quick video tutorial on how to use this system in a little bit. My first attempt at it took half an hour and was fairly shitty. I'll be doing a really fast overview this time around, so look for it. Also, a few tricks here. Rooves are objects, so you need to memorize some shortcuts within DM's map editor to work with them efficiently: Shift+left click will delete the topmost object at the location you are clicking on. Ctrl+Shift+click will select the object under the mouse cursor as the active object. For tall rooves, I suggest using pixel_z to offset them by a couple of tiles on roof objects. And if you have placed a bunch of roof objects in a building, but want to see inside the building to edit it, leave this line in your code somewhere: //#define HIDE_ROOVES 1roof layer = ROOF_LAYER#ifdef HIDE_ROOVES alpha = 96#endif  Simply uncomment the HIDE_ROOVES definition and recompile. Switch over to your map, and the roof objects will all be transparent enough that you can work on the inside of the building without problems. Remember to comment the line out again when you are done, otherwise the alpha is permanent for roof objects at runtime.
 Mar 1 2015, 5:16 pm (Edited on Mar 2 2015, 10:00 am) Here's an overly long and boring video that shows you how to use the setup to map out your rooves and buildings. Or, if you'd just like to take a look for yourself: Demo Library
 Mar 1 2015, 7:44 pm One thing that should be noted is that most if not all preexisting roofing implementations predate the introduction of image.override. Therefore the image-based implementations tend towards "positive" forms where all roofs are all /image objects that get added to the clients and removed as needed, rather than "negative" ones where the roof is always visible until an image overrides it.
 Mar 1 2015, 7:45 pm All of them do. I'm not sure anyone is actually aware of image.override. I've never seen anyone actually use it.
 Mar 2 2015, 12:34 am (Edited on Mar 2 2015, 1:27 am) Alright, this took a lot longer than I thought it was going to due to a couple of weird BYOND bugs. The code is up. A video showing how I construct these roof objects will Be coming up shortly. For now, here's a nice little gif: Special thanks to Lige and MDC for volunteering to help me test. (That's MDC in the gif)
 Mar 2 2015, 2:38 am Okay, everything is finished. Video, Library and Demo are up in the third post.
 Mar 2 2015, 5:39 am You mentioned running into bugs, but what were they? The only thing I saw specifically was that you said there was flicker from animate(), but I need more detail than that.
 Mar 2 2015, 5:42 am Don't worry Lummox, I'll hit up the bug reports forum shortly.
 Mar 2 2015, 11:35 am This is great! It's not as abstract as the previous Snippet Sundays. It gets right to the point of explaining how to implement an actual, useful feature for games. I will admit that I overlooked the usefulness of image.override. I never really thought about ways that it could be exploited like that. This lesson made me realize that the image.override feature has many interesting applications, so thank you for that. I hope to see more snippets like this one.
 Mar 2 2015, 7:29 pm Yeah, the abstract in this one was dominated by the example I used to show how to use the little abstract features I wanted to teach (appearance/overlay list unique id references and the override variable). Turns out that atom.override was only implemented in 2011. I thought it was a few years older than that.
 Mar 4 2015, 11:39 am Beautiful roofing system. I'll have to try and remember to study on it awhile later to see how exactly you did it; I'll probably learn something useful. As for image.override, I think I've used it once in the past as an admin verb to make me invisible to a selected player (for goofing around). Other then that, it's quite difficult to think of a useful way to use it, although I get the gut feeling there are several. I suppose that's just something I'd need to make a complete game in order to find.
 Mar 4 2015, 12:30 pm I've used it a bunch of ways. I wrote a system that uses it just the other day for floating damage numbers. The numbers show up in orange for observers to the attack, the numbers show up in yellow for the person who did the attack, and the numbers show up in red for the person on the receiving end of the attack. --There are a ton of good uses for the feature.
 Mar 4 2015, 2:38 pm In response to Ter13 Ooh, that's a neat trick. I always envisioned something like image.override as a disguise feature, something you could use for a game like MLAAS for instance. In my very first BYOND game I thought it might be interesting to disguise a mob, but couldn't think of a way to do it without giving the images to everyone.
 Mar 4 2015, 3:46 pm I always envisioned something like image.override as a disguise feature, something you could use for a game like MLAAS for instance. In my very first BYOND game I thought it might be interesting to disguise a mob, but couldn't think of a way to do it without giving the images to everyone. I had the same thought at first when I first discovered it. Out of curiosity, does the name of the override image show up in the status bar on hover, or does that always use the name of the atom? I never keep the status bar around in my interfaces, so I don't know. If the name of the image shows up, it could be really effective for disguises.
 Mar 4 2015, 4:10 pm The name is part of the Appearance, so it should override.
 Mar 25 2015, 3:13 am I've been reading all of your Snippet Sunday's to pass the time as of late and I'm consistently impressed. Cheers to another good read, Ter13.