ID:45397
 
Part III of a BYONDscape Classic! The markup for this article was converted by hand (and partly by Python script) from the old BYONDscape format; if you spot errors, please note them in the comments. Thanks! -- Gughunter


XI. Conquering the multi-dimensional list

Now that you’ve learned how to do list associations, I will teach you how to make multi-dimensional lists. Think of them this way: they are lists of lists.

Another way to see it, but what I find as a more confusing way to look at it, is to look at regular 1D lists as a line of values, 2D lists as a square or rectangle of values, and 3D lists as a cube or rectangular prism of values. I feel this to be more confusing because this definition alone doesn’t help the person figure out how to access the values.

Think of it this way: You have a list, and you have values in that list. Select one of the values. That value is another list. Now, you have a new list to work with. Inside that new list are some values. Each of those are lists. When you select one of those values, you can access another set of lists.

Your DM map files can make it easier to imagine multidimensional lists. Imagine how in your map files, you can go across the x axis 5 tiles, and then go up the y axis 8 tiles. That tile would be at position (5,8) in that 2D list. Map files are 3D, however. Define some more z levels and you could go across the x axis 5, go up the y axis 8, and then go up the z axis 3. That tile would be at position (5,8,3) in the 3D list.

A fun fact: When the 4th dimension comes up in conversation, it often refers to time. So say you had some sort of a 3D movie playing on a holographic display -- maybe showing all of the map levels in a game simultaneously, stacked in layers -- and had 100 seconds in the movie at one frame per second. If editing that movie was anything like editing the map in DM, if you wanted to get a tile in the 56th second, you’d access tile, say, (68,23,5,56). That would access the tile (68,23,5) at the 56 seconds mark.

Note that the more dimensions you have, the more enormous something like that would become and the more difficult it would be for computers to handle it. In that 100-second clip -- with each second being a different "snapshot" of an entire 100x100x100 BYOND world -- you'd have 100x100x100x100, or 100 million, tiles.

Working with Multi-dimensional lists:

Multi-dimensional lists can be defined as a regular element, or as an association in a list association. You cannot, however, define it as the index of a list association. There is an odd malfunction in DM that will turn it into a text string if you do so.

Example of the malfunction:

proc/Proc1()
var/list/Sector11Names = list("Jared","Wilbur","Warren","Joel","Luis")

var/list/Sector12Names = list("Cara","Paul","John","Greg")

var/list/Sector13Names = list("Doggimus","Fluffinator","Crumpft","Fubar")

var/list/AmericanPlant = list("Sector 1" = Sector11Names,"Sector 2" = Sector12Names,"Sector 3" = Sector13Names)

var/list/Sector21Names = list("Herold","Cindy","Bill","Bob","Jeremy")

var/list/Sector22Names = list("Hans","Kim","Catey","Lucas","Tom")

var/list/CanadianPlant = list("Sector 1" = Sector21Names,"Sector 2" = Sector22Names)

/////////////////////////////////////////////

var/list/EmployeeNames = list(AmericanPlant = CanadianPlant)

The very last line of that example shows a list associated with a list. You would not be able to access the AmericanPlant list through the EmployeeNames list because it was set as the index of a list association. If you were to output EmployeeNames[1], it would output the text string, "AmericanPlant", instead of the list you defined. If you were to output EmployeeNames["AmericanPlant"] (It’ll have to be a text string because that is what the list was turned into in that list), it would output "/list" because the list in the association stayed intact.

As seen above, the best way to show an example of multi-dimensional lists is lists of the same thing, but being categorized deeper. In the examples I will mostly use this chapter, it is a shown as a list of industrial plants. Each of those industrial plants are then categorized into sectors. Each of those sectors have employees in them.

Example:

var/list/Sector11Names = list("Jared","Wilbur","Warren","Joel","Luis")

var/list/Sector12Names = list("Cara","Paul","John","Greg")

var/list/Sector13Names = list("Doggimus","Fluffinator","Crumpft","Fubar")

var/list/AmericanPlant = list("Sector 1" = Sector11Names,"Sector 2" = Sector12Names,"Sector 3" = Sector13Names)

We have 3 lists (one for each sector) above containing the names of the people in Sectors 1, 2, and 3. We then define another list called AmericanPlant. This list contains lists -- it is a matrix. This uses associations as the lists for better categorization. The lists are indexed by the sector names.

I could have also done this:

var/list/AmericanPlant = list(Sector11Names,Sector12Names,Sector13Names)

That would be a matrix as well, but if we wanted "Sector 2"’s employee names, we’d need to remember that element 2 was Sector 2. It is, as always, a lot nicer to use associations to categorize. That way, no matter the position of the Sector 2 list in the matrix, we could always refer to it by AmericanPlant["Sector 2"].

In the above examples, AmericanPlant was a 2-dimensional list, meaning it was a list that contained lists, and those lists contained values.

We will now extend it into a 3D list:

proc/Proc1()
var/list/Sector11Names = list("Jared","Wilbur","Warren","Joel","Luis")

var/list/Sector12Names = list("Cara","Paul","John","Greg")

var/list/Sector13Names = list("Doggimus","Fluffinator","Crumpft","Fubar")

var/list/AmericanPlant = list("Sector 1" = Sector11Names,"Sector 2" = Sector12Names,"Sector 3" = Sector13Names)

var/list/Sector21Names = list("Herold","Cindy","Bill","Bob","Jeremy")

var/list/Sector22Names = list("Hans","Kim","Catey","Lucas","Tom")

var/list/CanadianPlant = list("Sector 1" = Sector21Names,"Sector 2" = Sector22Names)

//////////////////////////////////////////////

var/list/EmployeeNames = list("USA Industrial Plant" = AmericanPlant,"Canadian Industrial Plant" = CanadianPlant)

In the above example, the line after the comment-line is the list EmployeeNames. This is a 3D list. It is a list that contains lists. Those lists contain lists, and those lists contain values.

So how could we access the lists within the first list?

We would probably have to use the [] operator at some point, either to access an association or to access the regular contents of the list via element number. What many people don’t realize about the [] operator is that you can use it multiple times on a single line to get into a different dimension in the list.

Example:

proc/Proc1()
var/list/Sector11Names = list("Jared","Wilbur","Warren","Joel","Luis")

var/list/Sector12Names = list("Cara","Paul","John","Greg")

var/list/Sector13Names = list("Doggimus","Fluffinator","Crumpft","Fubar")

var/list/AmericanPlant = list("Sector 1" = Sector11Names,"Sector 2" = Sector12Names,"Sector 3" = Sector13Names)

var/list/Sector21Names = list("Herold","Cindy","Bill","Bob","Jeremy")

var/list/Sector22Names = list("Hans","Kim","Catey","Lucas","Tom")

var/list/CanadianPlant = list("Sector 1" = Sector21Names,"Sector 2" = Sector22Names)

//////////////////////////////////////////////

var/list/EmployeeNames = list("USA Industrial Plant" = AmericanPlant,"Canadian Industrial Plant" = CanadianPlant)

world EmployeeNames["USA Industrial Plant"]["Sector 1"][4]

The above example would output "Joel" to the world, because we accessed the list indexed by "USA Industrial Plant" in EmployeeNames, and in that list, accessed the list indexed by "Sector 1", and then we used element number to access the 4th element in the sector 1 list. The 4th element in it is "Joel".

Now that you know how to dive into the matrices, let's use for() loops to access all of the elements of all of the lists:

mob/verb/Proc1()
var/list/Sector11Names = list("Jared","Wilbur","Warren","Joel","Luis")

var/list/Sector12Names = list("Cara","Paul","John","Greg")

var/list/Sector13Names = list("Doggimus","Fluffinator","Crumpft","Fubar")

var/list/AmericanPlant = list("Sector 1" = Sector11Names,"Sector 2" = Sector12Names,"Sector 3" = Sector13Names)
//2Dlist

var/list/Sector21Names = list("Herold","Cindy","Bill","Bob","Jeremy")

var/list/Sector22Names = list("Hans","Kim","Catey","Lucas","Tom")

var/list/CanadianPlant = list("Sector 1" = Sector21Names,"Sector 2" = Sector22Names) //2D list

/////////////////////////////////////////////

var/list/EmployeeNames = list("USA Industrial Plant" = AmericanPlant,"Canadian Industrial Plant" = CanadianPlant) //3D list

//Now that we have a single list containing references to the other
//lists, we will only need that list to access all of the values of the other
//lists:

for(var/T in EmployeeNames) //T would equal the normal element (and it
//will be used as an index as well) each loop.

world "[T]:" //And since T would equal the normal element, we can
//output it to output the names of the industrial plants.

for(var/T2 in EmployeeNames[T]) //On this line, we have
//EmployeeNames[T], which is the association to the indices, that association
//being a list. We define T2 as the regular elements contained in the list
//(EmployeeNames[T] is the list) each loop.

world "--[T2]" //This would output the regular elements in
//the list EmployeeNames[T]. T2 would be the sector names that are used as
//indices in that list, in other words.

for(var/T3 in EmployeeNames[T][T2]) //On this line, we have
//EmployeeNames[T][T2], which is the association to the indices in the Sector
//lists, and is a list. This list only contains values.

world ">>>>[T3]" //Output the values in list EmployeeNames[T][T2].

The above example would output this to the world:

USA Industrial Plant:

-Sector 1

>>>>Jared

>>>>Wilbur

>>>>Warren

>>>>Joel

>>>>Luis


-Sector 2

>>>>Cara

>>>>Paul

>>>>John

>>>>Greg

-Sector 3

>>>>Doggimus

>>>>Fluffinator

>>>>Crumpft


>>>>Fubar

Canadian Industrial Plant:

-Sector 1

>>>>Herold

>>>>Cindy

>>>>Bill

>>>>Bob

>>>>Jeremy

-Sector 2


>>>>Hans

>>>>Kim

>>>>Catey

>>>>Lucas

>>>>Tom
You don’t always need to use associations like above, but it is generally good practice to for these kinds of lists where you’d need the name of the lists.

In the next example, I will not use associations in the list EmployeeNames in order to show you that you can have lists of lists without associations:

proc/Proc1()
var/list/Sector11Names = list("Jared","Wilbur","Warren","Joel","Luis")

var/list/Sector12Names = list("Cara","Paul","John","Greg")

var/list/Sector13Names = list("Doggimus","Fluffinator","Crumpft","Fubar")

var/list/AmericanPlant = list("Sector 1" = Sector11Names,"Sector 2" = Sector12Names,"Sector 3" = Sector13Names)

var/list/Sector21Names = list("Herold","Cindy","Bill","Bob","Jeremy")

var/list/Sector22Names = list("Hans","Kim","Catey","Lucas","Tom")

var/list/CanadianPlant = list("Sector 1" = Sector21Names,"Sector 2" = Sector22Names)

/////////////////////////////////////////////

var/list/EmployeeNames = list(AmericanPlant,CanadianPlant) //No
//associations, just a list containing lists.

for(var/T in EmployeeNames) //On this line, T will equal a list during
//each loop, because the lists are the regular elements in EmployeeNames

for(var/T2 in T) //Since T is a list, we can access it just by
//using for(var/T2 in T). We still wanted the sector names, however, for
//whatever reason, so T2 is equal to the index this time, unlike above.

world "--[T2]" //Output the sector name

for(var/T3 in T[T2]) //Now, since T2 is NOT a list, but an
//index, in order to access the association (which is a list of employee names
//for that sector), we will have to use [] on list T to get the association.

world ">>>>[T3]" //Output the employee name.

The above example would output this to the world:

--Sector 1

>>>>Jared

>>>>Wilbur

>>>>Warren

>>>>Joel

>>>>Luis

-Sector 2

>>>>Cara


>>>>Paul

>>>>John

>>>>Greg

-Sector 3

>>>>Doggimus

>>>>Fluffinator

>>>>Crumpft

>>>>Fubar

-Sector 1


>>>>Herold

>>>>Cindy

>>>>Bill

>>>>Bob

>>>>Jeremy

-Sector 2

>>>>Hans

>>>>Kim

>>>>Catey


>>>>Lucas

>>>>Tom
Notice how there aren’t names for the industrial plants. That is because in the list EmployeeNames, we did not use list associations, we just made the normal element equal the list.

Constructing environments using multi-dimensional lists:

As I said earlier in the chapter, the DM map can be seen as a multi-dimensional list of tiles. Using this information, you’ll know that a multi-dimensional list can be used to create an environment.

Your actual map is a pain in the butt to look around in DM. If you were to, say, generate an environment using that map, it’d hit the processor’s CPU pretty hard, because it would have to access each of those objects.

Instead, we will create a raw map. No objects, no graphics... Just Boolean vars. We can make 1s be floor, and 0s be walls. Example of a 2D list that defines an environment:

00000000000000000000000000000000000000000
00011111000011111000000000000000000000000
00011111000011111000000000000000000000000
00011111111111111000000000000000000000000
00011111000011111111110000000000000000000
00000000000011111111110001111111111111100
00000000000000000001000001111111111111100
00000000000000000001000001111111111111100
00000000000000000001111111111111111111100
00000000000000000000000001111111111111100
00000000000000000000000000000000000000000
See how that goes down? This is how random map generators are done.

You could also use text characters instead. # being walls, . being floors, % being doors, * being treasure chests, and = being stairways, for example.

Another advantage to this is it allows you to use nodes that stand for the objects on the map instead of the actual objects. This allows you to declare a node size and have the above example be 2 times bigger than it used to be because 1 node is equal to 1 square list of tiles on the map.

Now imagine a 3D list of nodes like that one, but extending in depth as well. Even in DM, this is how you’d make a 3D engine for use of games. DM has limitations on 3D, though, like no pixel detection, being tile-based, and slow graphics updating -- so I don’t suggest going out and making the next big 3D RPG on BYOND.

XII: Avoiding data pile-up

Before you program your game, you need to think about how to prevent data pile-up from making your game slow or preventing your game from running.

There are limits on just about everything in DM:

65,535 limit on /objs in memory

65,535 limit on /datums in memory

65,535 limit on text strings in memory

etc...

Turfs don’t abide by quite as low a limit. There may be a similar limit on unique turf types, but for turfs on the map, you can have a whole lot more (the map editor supports worlds with 1000 units in each dimension).

This means that every object you put into the world puts the world closer to the limit, in which it will not be able to create any more of those objects. It may not seem that you would be likely to hit such a limit in most games, but remember this: every object you create has a whole bunch of vars. Each of those vars makes the world closer to hitting a certain var limit.

var limits are harder to hit than object limits usually, but poor programming practices tend to actually make people hit those limits.

In chapter III, I taught that vague type paths were bad. This is one of the main reasons.

When you define this:

mob
var/PlayerNumber = 0

Player
var/PlayerID = ""


Monster
var/MonsterID = ""

The vars Player ID and Monster ID would only be located on specific mobs, and even though PlayerNumber is supposed to be specific to players, it is included with both players, monsters, and anything else defined as /mob. This means that all /mob/Monsters will have a useless var that will be making the world closer to that limit.

The other main cause of data pile-up is leaving objects around when they are no longer needed. This is a huge obstacle in creating any isometric or 3D game, and can be an obstacle in many other games. I will use my game Fate as an example.

When I first completed the game, I didn’t know much about data pile-up. The game already has an enormous amount of objects within lists on lots of mobs, but then I pulled something even worse: I didn’t get rid of objects that were no longer needed. This included blood laying on the ground, and people’s corpses. Those corpses also often had lists of objects attached to them that they could no longer use.

After a few hours of play, my game would mysteriously start hitting object limits. This was because I left all of those corpses and all of that blood around as active objects.

To fix this, I made the un-needed objects delete after a certain period of time. This made sure that my world wouldn’t hit that limit again.

When you are making a game in DM, ALWAYS keep those limits in mind while you are programming. If you ignore them, they can kill your game after it is already completely made, and in order to fix it, you’d have to redo your ENTIRE code. No fun. At all.

XIII: Using server-side and client-side savefiles

Savefiles are the final bout of programming in DM.

They are primarily used to save values in between player sessions and server shutdowns, or in the case of client-side savefiles, to allow the player to keep stats/values on different servers.

They can also be used to avoid data pile-up, by allowing you to save data into files and load it up when you need it instead of constantly having it in memory.

Creating a savefile object:

The first step in using savefiles is to create a savefile object that you can work with.

Define it as a savefile object so you can access its properties, and then make it equal new() to create it (as if you were creating, say, a new /obj or /mob):

var/savefile/S = new()

That would create a savefile object (connected to a file). We did not put an argument into new(), however, so it would just be a temporary file that is destroyed once we are no longer using the savefile object.

In almost all cases, you will want it to be permanent (until the file itself is deleted), so you will want to define a path/filename as the argument of new():

var/savefile/S = new("savefiles/file1.sav")

That would create a new folder called "savefiles" in your game's folder, and it’d put a file into that folder named "file1.sav". So the file you created would be located at, for example: C:\Program Files\BYOND\MyGame\savefiles\file1.sav. See how it’s created in the game folder?

Note that in DM, any paths you define will be defined with forward slashes (/). Backslashes (\) are used as text shortcuts in DM so do not define paths with them.

Note that you can make any number of levels of folders:

var/savefile/S =
new("savefiles/[copytext(ckey(src.key),1,2)]/[ckey(src.key)].sav")

The above would make a savefile in the player’s key, and sort it by the first initial of their key. If Dantom were to have their file saved with this, it would save it as "savefiles/d/dantom.sav".

Writing values into a savefile:

You know how when you use an associative list, you access/edit the association with list[element/index] = value? Well, writing values to a savefile is similar to this. Instead of list[element/index] = value, it has the format: savefile[index] = value.

Don’t think it’s too close to lists, though, because savefiles are still quite different. For one thing, instead of using = to make the association equal the value, we are going to use the << operator to write the value:

var/savefile/S = new("Savefiles/file1.sav")

S["Index1"] << "Hello!"

Notice how the arrows point into the savefile.

As with lists, you can edit the value after it is created, in the same way you created it:

var/savefile/S = new("Savefiles/file1.sav")

S["Index1"] << "Hello!"

S["Index1"] << "Bonjour!"

That would set Index1 in the savefile to "Bonjour!".

Then, obviously, you can write as many values as you’d like in this way. Just make sure the indices are different:

var/savefile/S = new("Savefiles/file1.sav")

S["English"] << "Hello!"

S["French"] << "Bonjour!"


S["Spanish"] << "Hola!"

Reading values from a savefile:

It may seem odd, but to load a savefile, when creating the savefile object, you load it the same way you create it. In other words, if it’s not there, it will create the file. If it is there, it will edit the file. It won’t overwrite the entire file on new(), it just sets the savefile object to the file specified:

var/savefile/S = new("Savefiles/file1.sav")

And now, to read the values in the file, the value can be accessed via savefile[index], just as above -- but you won’t be writing the association as above. You’ll just be accessing it.

It is generally a good practice to set a var equal to the value instead of accessing it directly from the file when you are using it.

The wrong way to use it:

proc/Proc1(mob/Player/M)
var/savefile/S = new("Savefiles/file1.sav")

world << S["English"]

S["French"] >> M.MainLanguage //When reading it, DON’T use = like
//you do with lists.

if(S["Spanish"] == "Hola!")
return 1

else if(S["Spanish"] == "Bien!")
return 0

Note how when I wrote the value to M.MainLanguage, I used >> and still had the index I was accessing on the left side.

That above is bad practice, because what if "French" were null and you didn’t correct it? M.MainLanguage would become null. What if they had a main language before? That would be overwritten with null, with no way to get back which language it was before.

The world << S["English"] line would always work because it’s just outputting what you want to know, but it’s still bad practice because if English weren’t a text string, you might not be outputting what you wanted to know.

The last access of the list, if(S["Spanish"] == "Hola!"), is also bad practice. What if there is no "Spanish" index? It would be null and the next conditional, which returns 0, wouldn’t process (of course, it wouldn't be a bad idea to check for the case where the Spanish index isn't either of the expected values and handle it properly).

Also, if you are reading from the savefile every time you need one of the vars, instead of writing it to a run-time var, you are using more CPU and possibly more hard drive access, which slows down your game.

The correct way to do it:

proc/Proc1(mob/Player/M)
var/savefile/S = new("Savefiles/file1.sav")

var/WorldOutput //Initialize the var

S["English"] >> WorldOutput //Read the savefile and write onto it.

if(WorldOutput) //Got to make sure you don’t output a blank line if
//nothing is there!
world << WorldOutput

var/Language

S["French"] >> Language //What is in index "French" goes into the
//Language var...

if(Language) //Now we won’t accidentally write over the player’s old
//language if nothing is there!
M.MainLanguage = Language

var/TestLang

S["Spanish"] >> TestLang

if(!TestLang) //If TestLang isn’t equal to anything after it was
//written to...
TestLang = "Bien!"

if(TestLang == "Hola!")
return 1

else if(TestLang == "Bien!") //This would now process if TestLang was
//null. It is also accessing a run-time var, so it doesn’t use as much CPU
//with accessing that savefile object again.

return 0

Don’t just save text strings, save everything:

You can write just about anything to a savefile. Strings, numbers, objects, lists, multi-dimensional lists, even icons.

One great thing about DM is that saving all of these different types of values is a snap:

savefile["index"] value

That would save any of the values I mentioned in full.

Reading them is only slightly more tricky. When reading an object in a savefile to write onto a var, make sure you define that var as the correct path before writing to it, so you can access it later in the proc:

var/obj/O

S["SavedObject1"] >> O

if(O)
O.loc = locate(1,1,1)

Now, with numbers, remember that in the savefile, a number would look exactly the same as a text string containing that number... So if you were to write a number into a savefile, and then read it later, the var you write to would then be a text string containing that number. In other words, you’d write it as 1885 and when you read it, it would be "1885". Simply use text2num() to convert it:

var/N

S["SavedNumber"] >> N

N = text2num(N)

N += 5 //Because of the line above, this would then not return a "type
//mismatch" run-time error.

Using "current directories" in savefiles (cd):

Before I go ahead, understand that the cd var and the dir list var that savefiles have are for INSIDE the savefile. They are referring to internal directories used by the file, not filesystem folders.

It is likely that you are going to want to be more organized in your savefile than just writing everything in the main directory.

Above, when we were writing things to the savefile, we were saving them in the directory "". That is the main directory, and can be accessed without changing the cd var.

As mentioned, you will need to use the cd var, which is a property of savefile objects. It basically stands for "The current directory you are now accessing within the savefile".

In order to write into a different directory, we define the cd var of the savefile object before writing:

var/savefile/S = new("Savefiles/file1.sav")

S.cd = "/Languages" //A new directory starts with a / -- just like DM's node tree.

S["Language1"] << "French"

S["Language2"] << "Spanish"

S["Language3"] << "English"

The indices "Language1’, "Language2", and "Language3" would then be written within the "/Languages" directory.

In the savefile, it would look something like this:

Languages

     Language1 = French

     Language2 = Spanish

     Language3 = English
I’ll tell you why this can have even more purpose that just organization in a moment. First, though, i will show you that you can have even more directories, and you can have directories within directories as well.
var/savefile/S = new("Savefiles/file1.sav")

S.cd = "/Languages"

S["Language1"] << "French"

S["Language2"] << "Spanish"

S["Language3"] << "English"

S.cd = "/Places"

S["Place1"] << "Paris"

S["Place2"] << "Mexico City"

S["Place3"] << "London"

In the savefile, that would then look something along the lines of this:

Languages

     Language1 = French

     Language2 = Spanish

     Language3 = English

Places

     Place1 = French

     Place2 = Spanish

     Place3 = English
You can also define more in-depth directories than just a single categorization:
var/savefile/S = new("Savefiles/file1.sav")

S.cd = "/People"

S["Person1"] << "John"

S["Person2"] << "Albert"

S["Person3"] << "Jacque"

S.cd = "/Attributes/Languages"

S["Language1"] << "French"

S["Language2"] << "Spanish"

S["Language3"] << "English"

S.cd = "/Attributes/Places"

S["Place1"] << "Paris"

S["Place2"] << "Mexico City"

S["Place3"] << "London"

In your savefile, that would then look along the lines of this:

People

     Person1 = John

     Person2 = Albert

     Person3 = Jacque

Attributes

     Languages

          Language1 = French

          Language2 = Spanish

          Language3 = English

     Places

          Place1 = French

          Place2 = Spanish

          Place3 = English
Now, when you want to access the values when they are in directories, just set the cd correctly before proceeding with the writing of those vars:
var/savefile/S = new("Savefiles/file1.sav")

S.cd = "/Attributes/Languages"

var/LangNeeded

S["Language1"] >> LangNeeded

The dir list var:

At some point, you may need to access a list of what directories or indices are in a savefile. Particularly you would need to access this list to do things like knowing what characters a player has saved if he has multiple characters.

The dir var is a list of all directories or indices within the "current directory" (cd). (If you don’t know what the cd var is, read the section above.)

So, say we saved this:

var/savefile/S = new("Savefiles/file1.sav")

S.cd = "/People"

S["Person1"] << "John"

S["Person2"] << "Albert"

S["Person3"] << "Jacque"

S.cd = "/Attributes/Languages"

S["Language1"] << "French"

S["Language2"] << "Spanish"

S["Language3"] << "English"

S.cd = "/Attributes/Places"

S["Place1"] << "Paris"

S["Place2"] << "Mexico City"

S["Place3"] << "London"

And then I wanted to know what exactly was in /Attributes/Places, especially if I don’t know what the names of the indices are (which might be the case if you save to the indices, say, character names, like "Gorlock". You wouldn’t know that the index was "Gorlock" without the dir var.).

Well, since the dir list is a list of all indices and directories in the cd you have defined, I could do this:

var/savefile/S = new("Savefiles/file1.sav")

S.cd = "/Attributes/Places"

for(var/T in S.dir)

world << T

That would output this to the world:

Place1

Place2

Place3
This, as you can see, is how we can know for sure what the index is that we need to access to get some value.

It also has directory names, so if you were to do this without defining a cd...

var/savefile/S = new("Savefiles/file1.sav")

for(var/T in S.dir)

world << T

...In which the cd would be the main directory, "", then it would output something like this to the world:

People


Attributes
Since those were the directories that were in the directory "", the main directory.

Client-side savefiles:

Savefiles that will allow people to keep their characters or stats from server to server can be very important in some games. To make it a client-side savefile, you will just need to make some minor adjustments to your server savefile code.

Note that players can only have a single client-side savefile per game (that’s game as in per entire game with its entire hub, etc.). That file is typically named after the hub number of that game. You will not need to know this, though, because the procs DM provides will handle getting that file. This also means that when it is client-side, you do not define a path in new() when initializing the savefile.

Before I show you the example, note that you will need a client to do it, because it is a client-side proc.

You will only need to fix up the initialization of the savefile object, and add an extra statement at the end of the file when you are done editing it.

We will be using client.Import(), which returns the savefile if there is one, and client.Export which saves it as client-side:

var/savefile/S = new(src.client.Import())

S["Person1"] << "John"

S["Person2"] << "Albert"

S["Person3"] << "Jacque"

src.client.Export()

That savefile would be just like any other savefile, but it would be on the players’ computers instead of always the server’s computer. It would save as the hub number of that game.

**Note that if the game doesn’t have a hub defined yet with world/hub, it will be "0.sav". This means it can get mixed up with your other projects’ client-side savefiles if they don’t have hubs, and will become obsolete when the world/hub var does get defined later on.

When we want to read from a client-side savefile, we do the same thing, but we read it, and we wouldn’t export it because we’d only be reading it, we wouldn’t be saving it:

var/savefile/S = new(src.client.Import())

var/PersonVar

S["Person1"] >> PersonVar

**Note that you will use client.Import() when both reading and writing.

Other savefile procs:

The fdel() proc deletes files or folders in a path your specify. It will only del from the game’s folder and folders in those folders, etc. Nothing before the game folder.

The fexists() proc takes a file path as an argument and returns 1 if the file exists, 0 if the file doesn’t.

The ExportText() and ImportText() procs allow you to import files as readable text strings and export them as readable text files.

XIV: A few miscellaneous tips

1. If you look at something in your code that needs to be re-done, and it looks like it would take hours to fix that one thing that needs to be re-done, you may need to re-think your programming techniques to avoid that.

2. Write design notes before you make a game, so you think through your goals and plans before you hard-code them.

3. Write comments in your code to explain what some vars and procs do, if you think there is any chance you might forget.

4. Remember: People play games mainly for gameplay. Make your game’s controls and interface simple, and make sure there is an actual GAME to your game... Otherwise, what would set it apart from a story or a movie?

5. Don’t be afraid to ask for help. The developer forums are your best resource.

6. If you lose motivation, talk to people and learn some strategies to make you motivated to work on your project again. There ARE strategies out there.

7. Always try to avoid data pile-up. You will regret it deeply if you run into problems later on because you decided to be lazy when you started. These problems could include things as serious as your game no longer running at all.

8. Program robustly, and make sure it’s flexible. It’ll be easier to edit and add onto later on.

It took but 20 minutes for me to fully understand savefiles (I only knew the basics before) and multidimensional lists. Great article!
Thanks for another great article
to long
Nickmick wrote:
to long

Because you should always take shortcuts when learning complicated things, right?
i cant find the previous 2 articles, you should have links up that connect them. looks like you went into a lot of detail and provided a nice bit of code. good work man thank you.
Expert Novice wrote:
i cant find the previous 2 articles, you should have links up that connect them. looks like you went into a lot of detail and provided a nice bit of code. good work man thank you.

Just search Kunark's Programming Tutorial in the search box towards the top of the Dream Maker's blog, and it should give you part 1 and 2.
i have full fod or tag you should part 1 and 2
Chibi-Gohan0 wrote:
Thanks for another great article

Hi i Love resident Evil online2 if i get Admin i well make a lot of icons ... please i need inforcer