proc/ReplaceSavedMobListWithKeys(savefile/F, varname)
if(varname in F.dir)
F.cd = varname
if(F.dir.len)
var/list/newlist = list()
// a list of keys should not have subdirs
for(var/sub in F.dir)
F.cd = sub
if("key" in F.dir)
var/newkey
F["key"] >> newkey
newlist += newkey
F.cd = ".." // go back up to IgnoreList
F.dir.Remove(sub)
F.cd = ".." // go back up to main atom
// replace the list in the savefile with this one
F[varname] << newlist
else F.cd = ".." // go back up to main atom
proc/ReplaceSavedMobWithKey(savefile/F, varname)
if(varname in F.dir)
F.cd = varname
if(F.dir.len)
F.cd = F.dir[1]
var/keyname
F["key"] >> keyname
F.cd = ".." // go back up to varname
while(F.dir.len) F.dir.Remove(F.dir[1]) // remove everything
F.cd = ".." // go back up to main atom
F[varname] << keyname
else F.cd = ".." // go back up to main atom
1
2
A more generalized solution, if it's needed for future vars like this:
|
In response to Lummox JR
|
|
Thanks alot for all the help Lumo. :)
|
@zasif, i'll sit down to write part 3 of my saving tutorial with you and papa bear in mind.
Your situation has reminded me of some stuff that needs doing to prevent pwipes. |
As Lummox pointed out, it's been standard practice to use the client as a screen object's location inside of new(), it was even documented that way when screen objects were first added. Although I do wish more education would have gone towards the concepts of shared screen objects and persistent screen objects.
It's a whole lot more friendly to add/remove from client.screen multiple times than it is to do that plus creating and deleting the objects over and over, especially ones that are used by all of the players without anything changing between them (frames, borders, etc...), to which you just have global objects for them and show/hide per-client, creating only a single instance of said objects shared across every client. |
have global objects for them and show/hide per-client, creating only a single instance of said objects shared across every client. Unfortunately, if your interface is designed reasonably well, this isn't possible. My interface approach maximizes the viewable area based on the size of the map. This means that the view may potentially be larger than the map's dimensions making parts of the screen appear off the edge of the screen. Every player has the potential to have a different resolution, thus I have to ensure that all UI elements potentially appear in different places for every client to keep them from being over the edge of the map's viewable boundaries. If you rely on global objects for the interface, that means every player in your world is clamped to a specific view size and that means that you are anchoring every player to the lowest common denominator. I argue that this is really bad UI design. |
In response to Ter13
|
|
Out of curiosity, why are screen objects relative to EAST and NORTH not valid options? I'm assuming you decided against them for some reason, but it seems to me that EAST-1,NORTH-1 should always be in view if you adjust the view size to the screen dimensions.
|
I'm glad you asked, Lummox.
It's because of the consequences of ideal screen sizes. First, we need to know the size of the map in order to calculate an ideal view size. Let's say we have a 1920x1080 viewport, and an ideal view area of 500 tiles. When I maximize my window, the map area expands to 1920x1039 (1080 minus 22 pixels for the title bar and 19px for the status bar). First, we take and divide both dimensions by the tile width and tile height to get a maximal tile number: 1920 pixels / (32 pixels * 1) = 60 tiles 1039 pixels / (32 pixels * 1) = 32.46875 tiles Then we round them up, because we don't want any black space on our map. If we rounded them down, there would be black space at the edges of the map, and that just looks ugly and unprofessional. So these values become: 60 tiles 33 tiles We want our map to be properly centered on our player, so we also need to make sure that the view size is odd. Thus, we add 1 if the width or height modulus 2 is equal to 0. 61 tiles 33 tiles Now we take width*height to get the tile area of the view. If the view is bigger than the maximum view area, we increase the scale of the map and run the calculation again: ceil(1920 / (32 * 2)) + (if even 1) = 31 ceil(1039 / (32 * 2)) + (if even 1) = 17 31*17 = 527 Therefore do it again at 3x scale: ceil(1920 / (32 * 3)) + (if even 1) = 21 ceil(1039 / (32 * 3)) + (if even 1) = 11 21 * 11 = 231 Now we have our ideal view size and map scale values of view 21x11 and scale 3 Now, if we do some real quick math, we'll discover that some pixels are going to go off the edges of the map. We call the number of pixels buffer_x and buffer_y. We can calculate them by subtracting the map dimensions in pixels from the view dimensions in pixels and dividing it by 2 times the map scale: 21 * 3 * 32 = 2016 11 * 3 * 32 = 1056 Therefore: (2016 - 1920)/(2 * 3) = 16 (1056 - 1039)/(2 * 3) = 2.833333 (rounded up to 3) This means that our buffer_x and y are 16 and 8. This means that WEST,SOUTH is 16 pixels to the left of the map's edge, and 3 pixels below map's edge in screen coordinates for this client. It also means that the EAST,SOUTH is 16 pixels to the right of the map's edge and 3 pixels above the map's edge in screen coordinates. Any object put at these coordinates will lose 16 and 3 pixels on each axis respectively. I wrote an interface library that accounts for this, forces all screen objects to use explicit anchors to one of the five available anchor positions, and then allows you to specify pixel offsets. Every uielement contains a group of hudobjs (the things that actually go into the screen list of the client) that ALL share the same screen-loc string. Every individual hudobj is positioned using transform.c/f within the screen rather than having a unique screen_loc, because screen_locs are such a giant pain in the ass to work with at an individual level. Also, it allows me to move entire UIelements merely by generating a single string. Further, it allows me to set an explicit width/height for uielements and the position update code automatically makes sure that if it's anchored to the top or right of the screen, the width/height of the element is taken into account when positioning the entire uielement and therefore all of its contents. This allows us to create resizable and movable ui elements quickly and easily without having to worry about generating dozens of strings and calculating all of this business at a per-object level. All uielements position hudobjects relative to the bottom-left corner of their parent element. All uielements position themselves from the five anchor points based on their left, right, top, bottom sides or center depending on the anchor itself. Further, each and every hudobj is enslaved by its owning uielement datum, which intercepts and drives all mouse and keyboard input within the datum itself and not within the hudobj. This makes writing complex user interfaces much more condensed into a single object and makes the entire model much faster and easier to work with. It also allows much more precise control over the overall UI state and gives a much more elegant and powerful interface system to work with than BYOND's default. Working with DM's interfaces is a pain to the point where I developed an entire API to make it painless. |
1
2
I'm gonna share the solution to the rollback here for future users, since Zasif shared the savefile. The offending var was easy to identify simply by searching for "type = /mob". If you look down in the file, it's clear that the second time this appears--where the file contains a "ride-along" mob--it's a member of IgnoreList.
So here's the problem: IgnoreList, aside from being initialized at compile-time which is a bad idea, was implemented as a list of mobs instead of a list of keys. So each player may or may not have other mobs in this list, and if they do then that player's current state saves when they do. The fix is twofold.
The first step, obviously, is to change IgnoreList so it works the way it should have from the beginning: It should be a list of keys, not mobs.
The second step, to avoid a pwipe, is this:
The code basically reads like so:
1) Before reading the mob using ..() (the default Read()), find out if there's an IgnoreList var.
2) If there is, use cd to go to that directory in the savefile. If this is a valid list of only keys, there will be no subdirectories.
3) If there are subdirectories, rebuild the list from scratch. Loop through each one and try to find a "key" var under each. If one is found, add it to the new list, and delete that subdir for good measure.
4) Finally, go back to the main mob dir and save the new list in place of the old IgnoreList, overwriting it completely.
5) Now the mob can be loaded by the default handler.