Map Instancing

by Forum_account
An easy way to create multiple instances of a map. Each instance is put on its own z level.
ID:119883
 
Keywords: mapinstancing
I created a library that lets you, at runtime, create and manage copies of maps. When a player enters a dungeon you can easily create a new copy of that dungeon just for that player - that way multiple players can enjoy the same dungeon but don't get in each other's way.

My goal was to make the library as simple to use as possible. The SwapMaps library provides this same feature, but I've been told it's not very easy to use (I've never used it myself, though). With my library, here's all you have to do to copy a map:

// create a new copy of the second z level
var/Map/map = maps.copy(2)

// move the mob to 5,3 on the new map
mob.loc = locate(5, 3, map.z)


The Map object contains some information and procs you can use to work with the new map copy. As you can see from the example, it has a z var which you can use to move players to that map. When the map is copied, the copy is placed on a different z level - you don't need to tell the library what z level to use, it finds an available one (it'll create a new one if it needs to).

The Map object also has some procs you can use. You can call map.repop() to repopulate the map. This will create new instances of objects that were initially on the map but have since been deleted - for example, if you kill an enemy it gets deleted, calling map.repop() will make that mob respawn at its original location. This is similar to BYOND's world.Repop() proc, except that it applies only to that copy of a map. This way you can repop a single instance at a time.

The map.free() proc tells the library that the map is no longer needed. This means that the library will re-use that map's z level. The library maintains a list of available z levels. When you create a map it uses an available z level, or, if none are available, it'll create a new one. Calling the map.free() proc will add the map's z level to the list of available ones. It's up to you to make sure that the map is not in use - you wouldn't want to free the map if players are still inside.

The MapBase Object

Before the library makes a copy of a map it first makes a MapBase object. This object stores all of the information needed to create a copy of the map. This way you don't need to keep an untouched, original copy of the map. You can create the MapBase object when the game starts up and then write over the original map - all of the information needed to create a map is stored in the MapBase object.

When you call maps.copy() the library automatically creates the MapBase object (if it doesn't already have one for the z level you're copying), but you can create these manually if you wish:

// create a MapBase for the second z level
var/MapBase/base = new(2)

// tell the library it can re-use the second z level
maps.clear(2)

// make a copy of the third z level
maps.copy(3)

// make an instance of the base
base.make()


This code makes a MapBase of the second z level then tells the library that it can re-use that z level. The third command copies the third z level - this copy will be placed on the second z level because we told the library it could use it. The last command makes an instance of the base. Even though we wrote over the second z level we have the MapBase object, which is all we need to make a copy of the map.

Future Work

When dealing with instanced maps, being able to create copies of a map is only part of the problem. The bigger problem is designing your game in such a way that everything works even when there are multiple instances of a map. You wouldn't want to do something like this:

obj
switch_03
proc/use()
for(var/obj/door_03/d in world)
d.open()

door_03
proc/open()
density = 0


When you have multiple instances of a map that has switch_03 on it, using the switch will open all instances of door_03 - even the doors in different map instances!

I'd like for the library to include additional demos and features that help you create maps that function effectively whether there's one instance or 10. I'm not sure what this means - I don't know what features you'd need. It really depends on how you use the library, so if you find a need for some feature, let me know and I'll see if I can add it.
You are AWESOME!
The difference between using this and SwapMaps and the /dmm_suite, is that both SwapMaps and the dmm_suite read map data that's not already loaded. They read files (SwapMaps uses the built in savefile format, if I recall correctly, and dmm_suite reads and writes .dmm files) that are in the directory of the executable at runtime, allowing for user defined maps to be loaded without having to recompile the game.

Under your system, it seems to me like you just copy all of the atoms already existing from a portion of the world map. Adding new maps to be instantiated would require rebuilding the VM binary.

Admittedly, this is still much simpler than using SwapMaps, but only marginally so over the dmm_suite.

SwapMaps requires that you process your dmm maps into the swapmaps map format before being able to load it at runtime.

However, your system only clones objects by type, and does not pass variables over. Vital things, like dynamic tag strings for identifying interactions or path weights, will not be cloned; Only the intial value of every variable is carried over.

Both SwapMaps and dmm maps keep track of variables of objects that have been changed, and will apply these variables whenever loading/instantiating a map.

[EDIT]: Not to mention, you are missing the ability to define object types that should not be cloned and should be skipped whilst cloning. An example being player mobs: Cloning a player mob has the potential to cause a lot of problems in a game.
None of that is terribly hard to add.

The library builds a "blueprint" of each map before making a copy so you can later destroy the original map. If you can save and load these blueprints, you can load maps that never existed in the dmb.

I don't remember the last time I edited an instance of an object on the map so I decided to make it store very little data. I can make the library store additional data if that's needed, but it ultimately comes down to what the user needs.

Not to mention, you need to add the ability to define object types that are NOT cloned. Things like player mobs should not be cloned onto an instantiated map and could cause problems whenever it is accidentally cloned.

There are lots of problems that can come up due to irresponsible use. I can provide tools to avoid these problems but I can't prevent these things from happening or add automatic safeguards against all problems. The map copy is made is based on a blueprint, so just create the blueprint before the map is filled with players.
There are lots of problems that can come up due to irresponsible use. I can provide tools to avoid these problems but I can't prevent these things from happening or add automatic safeguards against all problems. The map copy is made is based on a blueprint, so just create the blueprint before the map is filled with players.

Other than being a safeguard, I can imagine a scenario where a few players are actively building a map (Minecraft style) which you want to clone, or for example unwanted weather effects on a map being carried over to the cloned map. What did you have in mind for this kind of scenario?
Toadfish wrote:
Other than being a safeguard, I can imagine a scenario where a few players are actively building a map (Minecraft style) which you want to clone, or for example unwanted weather effects on a map being carried over to the cloned map. What did you have in mind for this kind of scenario?

The purpose of the library is to let the game create additional copies of a map. A player enters a dungeon and they get their own copy of it. I'm not sure why you'd copy a map that players are actively using, so I'm not sure how to handle it.

The library lets you save map "blueprints". When you create a MapBase object you can save it to file. Then, you can remove the original map from the .dmb, run the game again, and create copies of the map that are loaded from file. The saving and loading is just to support the same idea of instance creation. I suppose the library should include some way to save and load instances of maps, but I'm not sure how people would use that or what they'd need it to do.

But, to answer the question, I could add some procs the library uses to determine what gets saved. For example, it'd call atom.can_save() on every atom on the map before saving it - if it returns 1 the atom gets saved. You can override this proc to change the library's behavior to do whatever you need. I can imagine something simple for mobs, like this:

mob
can_save()
if(client)
return 0
else
return 1


But the criteria is ultimately up to the developer using this library.
I mentioned it in the previous comment, but I did just post an update which lets you save and load MapBase objects. The MapBase object is all you need to create an instance of a map, so you can create the MapBase, save it, remove the original map from your game, and be able to create instances of the saved map.

Here's how you use it:

// Saving:
// create the MapBase for the second z level
var/MapBase/base = new(2)
base.save("dungeon.map")

// Loading:
var/Map/map = maps.copy("dungeon.map")


Loading a map from file is done just like creating a copy of an existing z level. It loads the MapBase from the file, creates an instance of it, and returns the Map object representing the new instance.
The purpose of the library is to let the game create additional copies of a map. A player enters a dungeon and they get their own copy of it. I'm not sure why you'd copy a map that players are actively using, so I'm not sure how to handle it.

For example, if a few Game Masters want to use in-game tools to build a dungeon of their own for players.

can_save() is what I had in mind too.
I'd advise against using BYOND's native object saving functions for the purpose of saving maps, because of the sheer size these files can reach. A small 50x50 map can easily reach dozens megabytes in size and take forever to load through BYOND's native functions.

I guess a couple megabytes isn't really all that big normally, but in the context of BYOND, several megabytes is massive.
Toadfish wrote:
For example, if a few Game Masters want to use in-game tools to build a dungeon of their own for players.

can_save() is what I had in mind too.

That sounds like it'd work.

What I'm not sure how I'd handle is if you had a castle map and created multiple instances of it, one for each player. The players could then customize their castles and save them. Their castle would be loaded for them when they log in again.

Saving map instances is a closely related feature, but at the same time the purpose is very different. Creating a custom dungeon at runtime and wanting to make copies for other users fits the purpose of the library. Having a can_save() proc would seem to make this easily possible.
If you make another library without finishing Tiny Heros first, I'll physically abuse you.
I love you...
Avainer1 wrote:
If you make another library without finishing Tiny Heros first, I'll physically abuse you.

What if I just update libraries? I've made most of the libraries I can imagine =)

Most of my libraries are now in good enough shape that I won't need to update them much in the near future. The only project that will potentially get in my way of working on Tiny Heroes is an RTS framework.

I actually have a decent amount of work done on an RTS framework, but I'm not as interested in working on it. I don't see many people interested in making RTS games and I'm not that interested myself. I'd only make it because I think that BYOND has huge potential for RTS games but they're difficult enough to make that you don't see many (any?) of them. They'd work great on BYOND because RTS games work great as multiplayer games, would be fine with a lower framerate (~20 fps), and it's not a problem to use tile-based movement.

I'm 90% sure the next thing I'll work on is Tiny Heroes.
Offtopic:
I made a few RTS games but like everything I lost interest and let it die

On topic:
Can you create something bad? Like,is it actually physically possible for you to do wrong FA?
The only issue I have is what Dark_Saber mentioned, the files can get pretty hefty and using this system in a way I want (Multiple players all building their own seperate world from a single hub server allowing easier interaction between them) is far too slow at the moment.
El Wookie wrote:
Can you create something bad? Like,is it actually physically possible for you to do wrong FA?

In this forum post I wrote "var/turf/t" instead of "var/turf/t = loc".

I do make mistakes but I actively work to fix them. Most people plunk down some text or code and call it a "tutorial" or "library", but then they're done and never work on it again. I think people are just happy to get their creation to a moderately complete state that they don't want to think "hmm, now what's wrong with this and how can I improve it?", they'd rather just say "phew, I'm glad I'm done with that!"

I also try to pick topics that are well-defined and straightforward to implement, like map instancing =)

The only issue I have is what Dark_Saber mentioned, the files can get pretty hefty

I'm not familiar with how savefiles work, are these files big because that's just how savefiles are? Or are they big because maps are big and there's a lot of data to save?

and using this system in a way I want (Multiple players all building their own seperate world from a single hub server allowing easier interaction between them) is far too slow at the moment.

I'm not entirely sure what you mean, but if there are any new features you'd need the library to have, just post a suggestion here.
I'm not familiar with how savefiles work, are these files big because that's just how savefiles are? Or are they big because maps are big and there's a lot of data to save?

I'm not familiar with the format either, but I think it's because the format is inefficient that they get so large, not because of data.
Forum_account wrote:
Avainer1 wrote:
If you make another library without finishing Tiny Heros first, I'll physically abuse you.

What if I just update libraries? I've made most of the libraries I can imagine =)
You win this one.
Most of my libraries are now in good enough shape that I won't need to update them much in the near future. The only project that will potentially get in my way of working on Tiny Heroes is an RTS framework.
BUT BUT I WANA BE A TINY-HERO.
I actually have a decent amount of work done on an RTS framework, but I'm not as interested in working on it. I don't see many people interested in making RTS games and I'm not that interested myself. I'd only make it because I think that BYOND has huge potential for RTS games but they're difficult enough to make that you don't see many (any?) of them. They'd work great on BYOND because RTS games work great as multiplayer games, would be fine with a lower framerate (~20 fps), and it's not a problem to use tile-based movement.

I'm 90% sure the next thing I'll work on is Tiny Heroes.
It better be ;o

Forum_account wrote:
I'm not familiar with how savefiles work, are these files big because that's just how savefiles are? Or are they big because maps are big and there's a lot of data to save?

Savefile format
Thanks a lot for this libary. I was looking for something like this for my idea. You are life saver :D
Forum_account wrote:
I'm not familiar with how savefiles work, are these files big because that's just how savefiles are? Or are they big because maps are big and there's a lot of data to save?

It's more about what kind of data is saved. If you save the map object into a savefile, it'll save every turf and object within the turf separately with attributes and internal object IDs and so forth regardless of whether or not the object has unique attribute values.


In contrast, the dmm format saves the map(s) as a 2d matrix of various characters, where each character is assigned the type paths of each object that inhabits the tile, and attribute initialization values that differ from the compiled defaults for each of the aforementioned object types.

The dmm format is more akin to stamping tiles with a tool, whereas the savefile format will just save everything individually and uniquely. The savefile format is fantastic for saving things that require object references and so forth (for example, a character with equipment), but the dmm format is designed to save terrain efficiently (since terrain typically doesn't rely on things like dynamic icon data or object references, which the dmm format will not save).

Let's say I had an object of type /obj/test and created an instance of this object whose dir is equal to 8 (the default is dir = 2). Then, let's say I placed multiple copies of this instance all over the map. The savefile format would treat each object uniquely and save them uniquely and separately whereas the dmm format would recognize the instances as copies and save only one copy.

Try opening a dmm in notepad.
Page: 1 2