ID:1775324
 
So, I intend to have Players be able to build Structures within this game, but I'm having trouble trying to figure out how to do this. At first, I thought an easy solution would be to simply create the Structures as a type of Object, which had density, and overlay this object with a special door turf when it was built, so that when Characters bump into the door, they would teleport to the map representing the inside of the building (I've already got the map instancing down thanks to Forum_Account's library).

However, as I continued adding more Structures, they became more complex. More specifically, Towers and Walls are supposed to allow Characters to walk on the top of them. Like a castle's tower/wall housing Archers on the top of them. This can't exactly be accomplished with the first method, so I looked into trying to build the Structures, turf by turf through a function. The complexity of it was.... Deep. More worrying, was the amount of strain this placed on the CPU. Then I came to the idea of possibly creating the maps of these Structures, creating copies of these maps using Forum_Account's library, and then overlaying the maps of the Structures onto the Main Map. I however, have not the slightest clue if this is possible, let alone, how to go about this / what to expect from doing this... I scoured the forums in search of something, but the closest I found to it was a small tidbit stating that you'd have to make both maps the same size and some other stuff which I wasn't too clear on involving opacity on objects.

Well, I'm just curious if anyone knows anything related to overlaying one map onto another, if it's doable, the costs of doing, anything whatsoever. Or, if you've got a better idea of how I can go about building these Structures that won't be too taxing to the CPU.

Cheers and thanks in advance.
Yes, it'd probably be better to have a set template to load/save instead of generating everything turf by turf.

I know SwapMaps allows you to load template blocks into same z level, it'll be easy to generate different structures interiors or exteriors via a mixture of templates. I'm not so sure whether forum_account's instancing has that option.

Another thing to keep in mind is that this should relatively have no CPU cost, if you load the x amount of bigger maps at startup, there's 0 reason to unload them.

If you experience any CPU issues you should describe in more detail what you're trying to do, there may be a way to solve that.

Unless you solved your problem on your own, I'm pretty sure my reply won't be extremely helpful but as you go in the direction of studying how to piece together templates, I'm sure you'll ask more direct questions that will allow me to help you further.

I really think that if one library does not provide you with your solution, you should try another. SwapMaps might really be helpful to you (If Map Instancing doesn't have a block copy/paste function and only does whole z levels).
Well, yes, MapInstancing does indeed allow blueprints (templates) for you to save, copy, and load. Blueprints are pretty much entire maps, but I don't see this as a problem, as I'd create opaque and dense walls wherever needed in each blueprint to make them suitable for being placed on another map. What I'm questioning really, is how to go about placing / loading these blueprints onto another Z-Level in the first place.

I envisioned myself making as many of the blueprints as required (say, the Tower Structure interior and exterior as two separate blueprints) and then working with them as efficiently as possible (say, having the exterior blueprint be created upon the world map and then creating an instance of the interior blueprint when a person enters that Structure). I'd also pretty much use strictly the blueprints to create these copies, as Forum_Account's MapInstancing doesn't require you to include the files if blueprints of it already exists (I'd only need to include them when I decided to change the blueprints' design, I supposed).

So, what I'm trying to figure out was if there was some way for you to naturally place a Map over another, but I'm getting the implication from you that's not the case?

If so, then, does SwapMaps takes care of placing one Map over another? It matters because I had already went ahead and used Forum_Account's MapInstancing library throughout most of the code revolving around Structures (and worked with some density checks on turfs beneath the exterior "Big Image" of the Structures to make them dense as a work-around), so I'd like to prevent having to undo all of that if I can (it works, but it's messy and not at all flexible if I decide to add more complex Structures in the future). That being said, if there's no other way around it, I've no problem with biting the bullet either.
I don't notice anything that does what you ask in map instancing, I'm well aware this however exists in SwapMaps.

The catch is that SwapMaps saves/loads maps from files, it doesn't take existing maps as is, you'd have to process them into templates first.

It's probably possible to modify map instancing to does what you seek, it shouldn't be too hard, somewhere around the library there will be a place where max x and y are determined and you can change that.

Another thing that might work is to try and have different sized maps, if the world maxx/y are different in the map file it'll save you the trouble.

Then the remaining question is how to load the map into a certain x,y position. I'm not so sure whether you can avoid touching the library's code for this but basically you need to find where the beginning x/y is and make it a variable accepted via proc arguments.

Whatever path you choose works, I'm not quite sure which is better, they both have their cons, if you're far in development with FA's library you'll probably want to go with that choice, let me know if you have more questions.
Okay, well, I'm currently sifting through LummoxJR's SwapMaps, and I'm seeing how this could be used with SwapMaps_CreateFromTemplate(), SwapMaps_LoadChunk(), and swapmap.LoCorner() procedures. That's at least, if I'm understanding this all here correctly.

Just to make sure I do understand this correctly...

Step 1:
I could make as many interiors and exteriors of Structures in as many maps as needed.

Step 2:
When I wish to create a Structure then I would Load the exterior map of that Structure through the SwapMaps_LoadChunk() and using its bottom-left corner (swapmaps.LoCorner()) to help me place it on the world map.

Step 3:
I then create the interior map of that Structure through SwapMaps_CreateFromTemplate() and link the door turf of the exterior to this map to deal with relocating and any subsequent things while dealing with the interior.

Is that about right?

Also there's something I noticed in the demo of the SwapMaps that makes me question this further..

When I create a map, it seems to add the dimensions of the map I'm currently on, into the creation of that map or something? Basically, I created TestMap1 to be a 20x20x1 tile. When I use the GetSize function and go to the actual map, I can see as much, though, the map I'm currently on increases in size equal to that. Then, when I create TestMap2 to be 15x30x1, TestMap1 has 20x30 as its Top-right corner, despite the GetSize function still says 20x20. Furthermore, TestMap2 becomes 20x30 despite GetSize function says it 15x30. Is it taking the highest values of both maps and making all maps the same size to suit? If so, I thought you could make maps of differing sizes (at least, the Help-File in the library suggested as much).
This is sort of what you need to do yes, it's very generalized though.


As for your concern, when all maps are loaded, you have only 1 max x and max y size for all of them, that is the largest.

Every z level is going to have the same max x and y.
Sorry for the slow response, I got wrapped in dealing with a lot of things the last couple of days.

Okay, so since you said it seemed quite generalized, I'll give you a snippet of my New(mob/M) for Structures so that you can decide for yourself where it stands:

if(SwapMaps_Load("(Exterior) [src.name]"))
var/swapmap/temp = SwapMaps_Find("(Exterior) [src.name]")
src.Exterior = SwapMaps_LoadChunk("(Exterior) [src.name]", temp.LoCorner())
if(src.Type == "Building")
for(var/obj/Furniture/Door/Ent in range(5, src.Exterior.LoCorner()))
Ent.Map = SwapMaps_Load("(Interior) [src.name]")
for(var/obj/Furniture/Door/Ext in range(round(max(src.Width, src.Height) / 2), Ent.Map.z1))
Ext.Map = temp
break
break


I personally think I'm attempting to link the Exit door incorrectly using Ent.Map.z1, but I'm not sure.
When does this occur? On mob/New() or a proc named New(mob/M) ?

I would be a little concerned if you often load/unload maps during runtime, it'd be probably recommended to not load maps often and only save/load when really required.

If it's one time operation, the heavy load could be excused but if during common gameplay maps are loaded and unloaded so frequently it'll be experienced as serious lag and might make the game unplayable.

Just an example of what I fear, do you know those games where a player logs in and suddenly there's a like spike, so in reaction, players think that relogging will solve their lag so they too relog and there a chain reaction is created where players constantly relog and maps are constantly reloaded generating more lag.

Few notes:
- SwapMaps_Load() returns the map, you could do that code instead:
var/swapmap/temp = SwapMaps_Load("(Exterior) [src.name]")
if(temp)


- If you planned to find one door, you should use locate() and not a for like this:
var/obj/Furniture/Door/Ent = locate() in range(5, src.Exterior.LoCorner())
if(Ent)
// other stuff

Same thing with the second door, this however wouldn't break your code, if there's a problem.

- Why do you use both Load and LoadChunk for the same template? To me it looks like "(Exterior) [src.name]" is loaded twice, once with Load and then it's loaded exactly in same location via LoadChunk, this looks like useless extra map loading.

- A method I personally like to easily locate things is actually to use locate("tag name"). You set tag var of a those doors with some ID then use locate to find it.
// on map or door variables set tag
door.tag = "Interior_MapName_UniqueID"

// then when you want to find and use it
var/obj/Furniture/Door/Ent = locate("Interior_MapName_UniqueID")


Do keep in mind if you load the exact same map, you'd have 2 tags with same name, that's why I added "Unique ID", in case you're loading the same map more than once, make sure to add another peice to the tag making it unique. If you remember to change tag each time you load a map or a chunk, it'll work just fine. It's just something to be aware of, to make sure only 1 unique tag exists.

Lastly I'd like to mention the details of load map and load chunk:
    SwapMaps_Load(id)
Load a map by its id

SwapMaps_LoadChunk(id,turf/locorner)
Load a swapmap as a "chunk", at a specific place. A new datum is
created but it's not added to the list of maps to save or unload.
The new datum can be safely deleted without affecting the turfs
it loaded. The purpose of this is to load a map file onto part of
another swapmap or an existing part of the world.
locorner is the corner turf with the lowest x,y,z values.
- This all occurs on New(turf/Loc) of any Structure being made. My mistake for typing New(mob/M)... Not even sure why I typed it.

- If I were to load all the Maps at runtime and then "find" them when needed to create the copies, won't this give me the effect of having all the maps accepting only the largest values as the maxx and maxy? Won't the occurrence I mentioned about in the demo simply occur again, making maps not have their true / intended sizes?

- It seems I somewhat misunderstood how LoadChunk worked. I was under the assumption it required you to have some form of swapmap specified first before using it. Corrected that. It does lead me to ask (based on the point before this) how this will work out if I have all the maps Loaded at runtime however. Won't this be technically loading an already loaded map? Afterall, I think I require both LoadChunk and Load as LoadChunk is the only one which allows me to place one map onto another (for the Structures' Exterior) and Load to be used normally (for the Structure's Interior).

- I've got a system to uniquely identify and tag atoms on my "To-Do List" so I can easily find things. The idea is to help me with identifying singular or multiple atoms by a view variables I've already defined as well as a unique ID Number. I was thinking of implementing this system to be ran upon the creation of any atom (New()), giving them a unique tag to be identified by immediately, however, I'm not sure how the procedure of placing swapmaps work (that is, if each turf or obj within the map is created through New() when using the swapmap functions) so that I could then create a unique tag for each turf or obj created, and then find my doors as such. This would also mean I wouldn't have the issue of having the same tag being created from a map of an atom as you mentioned.

- As it relates to all of your other notes, thanks, I've made the corrections to those just now.

The Code as it is now:
    New(turf/Loc)
// ...
src.Exterior = SwapMaps_LoadChunk("(Exterior) [src.name]", locate(Loc.x-2, Loc.y-2, Loc.z))
if(src.Type == "Building")
var/obj/Furniture/Door/Ent = locate() in range(5, src.Exterior.LoCorner())
Ent.Map = SwapMaps_Load("(Interior) [src.name]")
var/obj/Furniture/Door/Ext = locate() in range(round(max(src.Width, src.Height) / 2), Ent.Map.z1)
Ext.Map = src.Exterior

// ...
1) You shouldn't fear having a little larger max x and y, just make sure to corridor off your maps properly where you need, it's quite common to have a larger max x or y than what you really need because another z level needs it. If you really have too much unused space and you want to properly use it, use LoadChunk on same z level on the unused areas.

But really unless you have like a ton of 5x5 rooms in a 100x100 map, I don't think it's all that much a issue if you have one map filled with 80x80 and another with 76x94 but there's blackness to fill on remaining tiles. It's better to waste RAM then constantly load/save/unload maps and create lag.

You really shouldn't worry all that much about your maps being larger than you intend.

2) If you're loading a new map, use Load, not LoadChunk. LoadChunk loads things to existing maps, which I imagine you'd want to use for exteriors and not interiors but if the z level of the exterior doesn't exist yet and you want to create a new z level from a template, load will do that.

If you want a blank z level that isn't loaded from anywhere, you can also do world.maxz++, making world max z larger creates a new z level for you to use.

3) If every atom has an unique ID that you are not aware of, then it beats the purpose of having an unique ID. In my opinion you only need unique IDs for objects you plan to use and know their ID. I also definitely recommend using tag var for this.

If every exterior and interior have a different name, then setting tag of the door to "(Exterior) [src.name] EntDoor" for exmaple would be just fine. If you have more than 1 exterior with same name you could do the following
// somewhere set this global var
var/uniqueObjCount = 0

New(turf/Loc)
// We load everything normally then access doors with the basic tag name
src.Exterior = SwapMaps_LoadChunk("(Exterior) [src.name]", locate(Loc.x-2, Loc.y-2, Loc.z))
if(src.Type == "Building")
var/obj/Furniture/Door/Ent = locate("Exterior_[src.name]_Door") Ent.Map = SwapMaps_Load("(Interior) [src.name]_Door")
var/obj/Furniture/Door/Ext = locate("Interior_[src.name]")
Ext.Map = src.Exterior

// now we modify the tags to to be unique
uniqueObjCount++
Ent.tag = "Exterior_[src.name]_Door_[uniqueObjCount ]"
Ext.tag = "Interior_[src.name]_Door_[uniqueObjCount ]"
// ...

Every time we load this named exterior, there's only one "Exterior_[src.name]_Door", after we're done loading and using those tags, we rename them so that the next time we load it, we can use the basic tag again.

Notice how I set rules like "Interior_MapName_Door" for tags, so that I remember whenever I want to call them exactly what tag it is, you should also define those rules and names based on your taste and habits, it's much better in my opinion than a completely gibberish ID that you set for every atom.

Another thing to note is whether the map save and when you really need to use those IDs, if you only use them once you can even just remove tags after you're done using them. If they're used often but you only require to know about them once, adding a unique mystery identifier is also an option. It all depends on what you do with it, I do however don't see a reason to give /every/ turf and obj an unique tag.

If you do however want a unique ID system, this lib might be helpful.

Finally, just a note about SwapMaps and turf tags, I have experienced some problems with tags on turfs after the map was saved and loaded, I haven't looked much into it, I don't really know much, I generally workaround that by using tags on objects.

I think it has to do with the fact turfs continue to exist even after a map was unloaded and if the map is loaded to a different z level, it's possible that a tag exists twice but this is just a theory. You should probably just use tags on objects to avoid this headache.

4) The code looks so much nicer now, good work! Cheers to progress.
- Oh well, if it's not that big of an issue concerning a map's max x and max y values, I guess it'll be fine.

- Alright yeah, then I'll use SwapMaps_Find to locate the Loaded Map for the Interior, yes?

- I dabbled a bit with what you showed me and with the library, but realized I'll probably still have to make my own tagging system regardless that uniquely identifies all (or maybe just, a lot of) atoms. The reason however, why I need to do this, is because I've got a Task System in place to give Players some control over AIs; that is, allowing them to carrying some orders / commands such as finding a person of interest, gathering resources, etc. So, I need the tagging system to be able to uniquely identify say, ore objects from tree objects, so that I can use them in the Task System for AIs to find, instead of just using a unique ID for a specific object, and having the AI search specifically for that object. I tried using .type, but then the Task System became more complicated over time (like searching for a specific type of person), so just having .type wasn't enough unless I wanted having the couple if-statements behind it to help uniquely identify the AI's "target", but then it didn't exactly make it flexible.

- And thanks a lot for the help, mate.
I didn't notice earlier but here's some important note:
SwapMaps_Load()
// returns the loaded map

SwapMaps_LoadChunk()
// returns success result, 1 or 0, it does not return the chunk loaded.


This is important because you do this:
src.Exterior = SwapMaps_LoadChunk("(Exterior) [src.name]", locate(Loc.x-2, Loc.y-2, Loc.z))

I hope you understand Exterior's value is usually 1.

As for your question, yes, you can find loaded maps with either SwapMaps_Find or
var/swapmap/map = SwapMaps_Load()

While it doesn't work for LoadChunk, it does work for Load. You also can't use Find to find chunks, the datum containing info about the chunk is deleted after it's creation.


Hmm, alright then. I've made the changes. How does this look then?

if(SwapMaps_LoadChunk("(Exterior) [src.name]", locate(Loc.x-2, Loc.y-2, Loc.z)))
src.Exterior = SwapMaps_Find("(Exterior) [src.name]")
if(src.Type == "Building")
var/obj/Furniture/Door/Ent = locate() in range(2, src.Exterior.LoCorner())
Ent.Map = SwapMaps_Find("(Interior) [src.name]")
Ent.Link = src
src.Door = Ent
var/obj/Furniture/Door/Ext = locate() in range(round(max(src.Width, src.Height) / 2), Ent.Map.z1)
Ext.Map = src.Exterior
Ext.Link = src
if(src.name == "Tower")
var/obj/Furniture/Ladder/LadIn = locate() in range(round(max(src.Width, src.Height) / 2), Ext.Map.z1)
var/obj/Furniture/Ladder/LadOut = locate() in range(2, src.Exterior.LoCorner())
LadIn.Link = src
LadOut.Link = src


I did it this way as I do need Exterior to be a map, so that when I begin dealing with the doors, I can teleport Players properly from the Interior to the Exterior (and vice versa). This also means that I'd have to load both Exterior and Interior Maps at runtime.

It does make me raise a question as it concerns the actual teleporting however..

Door
name = "Door"
State = "Unlocked"
icon_state = "Door - Closed"
Interactable = 1
Interactions = list("Lock")
var
Key
Link
Angle
Locked
swapmap/Map

Entered(mob/M)
if(Locked == 0)
// Opening Door Sound Effect
flick("[src.name] - Opening", src)
src.icon_state = "[src.name] - Opened"
M.loc = locate(Map.x1+2, Map.y1, Map.z1)
sleep(7)
// Closing Door Sound Effect
src.icon_state = "[src.name] - Closed"
else
// Knocking on Door Sound Effect
return


What I'm trying to do here, is to teleport the Player to the point precisely atop of the Interior / Exterior as needed. I'm questioning if this would work correctly (line involving the locate() to be specific).
src.Exterior = SwapMaps_Find("(Exterior) [src.name]")

Ext.Map = src.Exterior

The Ext door should not work according to your code because Exterior is not a map and shouldn't have the variables x1, y1 or z1. It actually should end up with a runtime error.

To solve this you should pass the location to doors on Exterior's New() because of course, you decide where to load the exterior chunk.
var/turf/loadLoc = locate(Loc.x-2, Loc.y-2, Loc.z)
if(SwapMaps_LoadChunk("(Exterior) [src.name]", loadLoc))

Ext.TargetLoc = loadLoc

Door
var
turf/TargetLoc

Entered(mob/M)
if(Locked == 0)
M.loc = TargetLoc
else
// Knocking on Door Sound Effect
return

You can also grab the location of interior door in New() and just transfer the loc reference to interior door.

Actually, all Structures are - .... Probably best if I show how that works instead.

obj/Structure
Core = "Structure"
density = 0
Craftable = 1
Endurance = 10000
var
Door
Keep
State
Rally
Width
Height
Action
Nation
Birthdate
Direction
swapmap/Interior
swapmap/Exterior
Time[0]
Tasks[0]
PayDay[0]
Purpose[0]
Employees[0]


So as you can see, Exterior and Interior are both swapmap variables. And it is swapmap variables which have the variables x1, y1, and z1, so it should be fine now, no?

In retrospect however, I did notice that just using the locations of the doors for their "teleporting-counterparts" would be significantly simpler like you said, so I went with that route.

EDIT
Something just occurred to me... I've done all of this but, has this actually made instances of the Exterior and Interior? I mean, what I've done is find both of them, and linked them but... I don't believe I've created actual instances of the maps. The purpose of Exterior and Interior were to hold the unique instances made of that Structure afterall.
What I've been trying to explain to you in the last 3 replies is that the instances are created in SwapMaps_Load. The problem you are experiencing probably resulted from the fact SwapMaps_LoadChunk does not create a swapmap instance, it returns 1.

Whenever you tried to access Exterior variable you got a runtime something along the lines of "can not read 1.x1".

In any case, if you went the simple location storage route, this problem should be resolved.

Hang on a second, there seems to be an issue in miscommunication / misunderstanding.

From what I understand, you're telling me, an instance is created through the use of SwapMaps_Load(). SwapMaps_Load() is used at runtime to load all maps for all Structures. It is at this point then, where I have 1 instance of all Structures. SwapMaps_Find() will only find maps which are loaded; that is, it'll only find 1 instance of say, "(Exterior) Barracks" and 1 instance of "(Interior) Barracks". However, I will need much more than simply 1 instance of these because a Player may want to create 2 Barracks. At this point however, it'd be having two seperate "Chunks" for the Exterior, but the same Interior when you enter them. That's what I'm trying to figure out is happening here, because if it is -- I need them all to be unique, which may mean I still have to use the SwapMaps_Load() for that Structure's Interior in New(turf/Loc) as oppose to having them all at runtime. This is again, unless, I can create "copies" of those that are already loaded in a much less taxing way.
Actually yes, if you have 2 "(Interior) Barracks" this is a problem because you don't have a way to identify each one, they're not unique.

If I recall correctly they're in a associated list. All Find does is attempt to access a member of that list via ID, if two items have same ID it would be a problem.

To solve that you can modify SwapMap with this tiny change to Load
proc/SwapMaps_Load(id, uniqueId = "")
InitializeSwapMaps()
var/swapmap/M = swapmaps_byname[id + uniqueId]

It adds a optional argument, this will only be effecting the ID in the list and not the map template name so it can load more than 1 with different names in the list.

I hope this is enough, I haven't tried it.

The second problem that I still don't think you understood is that you have no instance for Exterior, since they're loaded with LoadChunk, their instance is deleted and what returns is 0 or 1.

Here's a few examples to explain this:
var/i_have_cookies
i_have_cookies = 1
// i_have_cookies now contains 1
i_have_cookies = new /obj ()
// i_have_cookies now refers to a new /obj
i_have_cookies = image(stuff stuff stuff)
// i_have_cookies now refers to an image that was returned by image proc.
i_have_cookies = SwapMaps_Load(stuff stuff)
// i_have_cookies now refers to an instance of swapmaps that was returned by Load
i_have_cookies = SwapMaps_LoadChunk(more stuff and other stuff)
// i_have_cookies now contains 1 because it loaded the chunk successfully. It does not refer to an instance of SwapMaps.
I was thinking I may not need to worry about the issue with SwapMaps_LoadChunk() as I may not even have a reason to reference the Chunk at all. From what I understood, I thought the Chunk would be loaded and remains there indefinitely like in the demo, which would be permanently altering the state of the world map. If that is the case, that's fine.

As it relates to the first problem, concerning loading multiple instances, I'm not sure that works because these instances are to be created on a fly from Players. Imagine a Ruler who creates a Barracks for instance. This doesn't mean he'll only have 1 or 2, as he may create as many as he wishes. As such, it means I'd have to create the ID based on how many other instances of Barracks there are in the world, and still create these instances during the New(turf/T) so the Ruler can do it, and not at runtime, as it means I'd have to set a maximum amount of Barracks allowed so I can set their ID Numbers.

EDIT: Also, if I have to create the instance only during New(turf/T), then there would be no need to save the ID in any such fashion, as I could then just link it to the variable for the Interior and always have it. This is for the Interior, that is.
You seem to be heading the right direction, if there's any question I missed, feel to point it out, right now I don't see any problems with the way you're thinking.