Jump to content

  • Log In with Google      Sign In   
  • Create Account

Banner advertising on our site currently available from just $5!


1. Learn about the promo. 2. Sign up for GDNet+. 3. Set up your advert!


JTippetts

Member Since 04 Jul 2003
Offline Last Active Yesterday, 11:50 AM

#4972795 Lua: A return keyword followed by a table

Posted by JTippetts on 23 August 2012 - 05:01 PM

Each file in Lua is compiled as a chunk and executed as if it were an anonymous function. So the return statement essentially boils down to a single anonymous function that returns a table. The returned table contains 4 inner tables, named description, metrics, texture and chars.

If the above source file were named script.lua for example, and you executed the line t=dofile("script.lua") then after execution, the variable t would hold a reference to the table created and returned by the above. This is one trick for using Lua as a data description language.


#4972480 Wrapping my head around a hex tile grid

Posted by JTippetts on 22 August 2012 - 10:39 PM

My current game project, Goblinson Crusoe, is a hex-based game. The way I handle it, though, is that the underlying scene manager is just a basic rectangular grid, for simplicity. The hex system is just a layer that sits on top of it. In this layer, I can convert a World coordinate to a Hex coordinate, and vice versa (although, when converting from Hex to World, I just calculate the World coord of the hex center). Objects, then, don't need to track their current Hex coordinate, but can just call the hex layer to calculate it when needed. The hex layer also provides other operations such as listing all neighbors of a given hex, listing all hexes within a given radius of a hex coordinate, and so forth; pretty much any kind of hex operation.

Movement and animation are all done in world coordinates. Pathfinding is done on the hex grid. So when a character wants to move, the pathfinder will try to find a valid path on the hex grid between the character's hex tile and the destination tile. This path is generated and returned as a list of hex coordinates. Since the game is a turn-based RPG fixed to the hex grid, this set of hexes is simply converted to a set of world coords representing the centers of each tile, and the character is progressed along the vector path thus produced.

Since the underlying scene structure is a simple rectangular grid, there is nothing weird to worry about as far as trying to manage the scene as a hex grid. Objects don't belong to a given hex, but they are maintained in the spatial grid. Frustum culling using regular grid cells is quicker than trying to do it with hex-shaped cells. Hexes are an appropriate abstraction for the game board, but not necessarily appropriate for the scene structure, and you just compound your work by trying to force the issue.


#4972425 Is python fast enough for a simulation heavy game similar to Dwarf Fortress?

Posted by JTippetts on 22 August 2012 - 05:48 PM

Washu hit on the key points for making sure that your game is fast when using Python, at least in my experience. Some parts should be offloaded to C++ modules. I always tended to look at it on the basis of abstraction. Some things operate best at a low level of abstraction. Bare-metal stuff, like physics and rendering. Other stuff works well at a higher level of abstraction, such as logic and AI. The logic and AI can live well inside Python, and in fact is very well served by living in Python, but the rendering, collision, physics and pathfinding stuff would probably be best handled by C++, built as a layer upon which the higher levels of abstraction can operate.


#4970528 Dynamic objects in Isometric Map Drawing algorythms

Posted by JTippetts on 17 August 2012 - 07:10 AM

1) The "funky value" for the foreshortening of the unit cube was calculated as (0.5/cos(30)) / (sqrt(2)). I don't really have time to do a diagram, but maybe a description will do. The vertical walls in the tile sprite are 0.5 times as tall on-screen as the top of the tile. The sprite is being viewed at an angle of 30 degrees. Doing a little trig, you can see that if the tile top is considered 1 unit in size on-screen (it's not, but for the purposes of the math it is) then the foreshortening factor to use to make a unit-size cube half that size in screen space would be 0.5 / cos(30). Now, since the tile top is actually being viewed diagonally, it's size is actually sqrt(2), so we need to divide (0.5 / cos(30)) / sqrt(2) to get the final foreshortening factor. Scale a tile-sized cube by this factor vertically, and it should match the sprite tile pattern.

(This kind of weird math is the most difficult part of tranforming 2D to 3D isometric. You just need to get a sheet of paper and draw some diagrams of exactly what is happening, as far as the interaction of a camera at 30 degrees / 45 degrees is concerned. Once you wrap your head around it, the rest is easy peasy.)

2) Not necessarily. I just have my mobs keep track of their world coordinate. There are 16 world units per tile. So a tile world 10x10 tiles in size in X and Z would be 160x160 units in size. To find the tile any given mob is in, you just divide his world coordinate by 16. Now, I usually do use a spatial grid structure to track mobs, but that is typically for visibility determination.

3) No, all you have to do is scale the ortho_width and ortho_height values when constructing your projection matrix by 1/zoomfactor. So if you want a zoom of 2, just scale by 0.5. To make it easy, you could just alter the applyCamera function:

void CIso::applyCamera(float zoomfactor)
{
	GLint viewport[4];
	glGetIntegerv( GL_VIEWPORT, viewport );

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	float aspect=(float)viewport[3]/(float)viewport[2];
	float screen_tiles=(float)viewport[2] / (float)tile_pixel_width_;
	float ortho_width=screen_tiles*(float)tile_world_size_*1.414213562373f * (1.0f/zoomfactor);  // Scale the horizontal by zoomfactor
	float ortho_height=ortho_width * aspect;
	glOrtho(-ortho_width/2,ortho_width/2,-ortho_height/2,ortho_height/2,0.0f, 1000.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0,0,-100);
	glRotatef(30,1,0,0);
	glRotatef(-45.0f,0,1,0);
	glTranslatef(-x_,0, -z_);
}

That'll let you zoom in or out to whatever level you desire, any time you desire. No resizing the GL viewport is necessary.


#4969684 Dynamic objects in Isometric Map Drawing algorythms

Posted by JTippetts on 14 August 2012 - 07:52 PM

The key to eliminating filtering, whether it's in Blender or in a game, is to ensure that there is exactly 1:1 correspondence between the pixels being drawn and the region of the buffer they are being drawn to. Any change in scale or stretching will result in filtering of some sort, and you will have to plan accordingly. You can use nearest filtering, but there will still be filtering. This is actually the biggest hurdle when trying to do a pixel-perfect translation from 2D to 3D. Although, of course, you also have to worry about filtering if you do traditional 2D with scaling, so there's that.

In Blender, you can reduce filtering by going to the Texture->Image Sampling panel, turning off Mip Maps and interpolation, and cranking down the filter size as low as it will go. But you also have to ensure that your object is scaled relative to the viewpoint correctly, so that there is a 1:1 pixel mapping. Also, I notice that in your posted image, on the right, there is a bit of sloppiness in your tile. To be pixel-perfect, or as near to it as can be achieved, you need to ensure that your tiles are correct. A 2:1 tile ratio has cell lines that are exactly 2 pixels horizontally for every 1 pixel vertically; in your tile, there are a couple spots on the cell boundary that don't follow the pattern precisely, and this can have implications when drawing. That is a minor nitpick, though.

Getting the math right for pixel-perfect is the trickiest part of the process. You also have to ensure the quality of the asset creation process. I have found that, for best results, a 2:1 isometric tile hold approximately to this template:
Posted Image

That is zoomed, of course. You can see that all the edge lines hold to the 2:1 line ratio. In the image, the dark zones indicate areas where padding may be placed, in order to mitigate what filtering may take place. Extra padding around the outside may be necessary if the sprite is to be scaled. The padding between the top and the walls should match the top, and not the walls, since those rows of padding will be visible on tiles of the same level placed adjacent to one another, without tiles on top of them.

The main areas where you need to do some calculating are:

1) The orthographic projection matrix, and how it relates to the drawing size of the tile cubes. My personal method is to visualize each tile, as in the template above, as being some number of units in size. Each 2-pixel wide block in the cell lines denotes a unit, so a tile that is 64 pixels wide would denote a 16x16 unit cell. So if I draw my cubes as 16x16xH (height varies, depending), and use integer math for coordinates and locations, then I can ensure that tiles will be drawn on whole-pixel locations, and filtering is reduced. This requires special consideration in calculating the projection matrix.

My method, then, of calculating the orthographic matrix is to divide the screen horizontal resolution by the width, in pixels, of a tile. (64, in the case of the above magnified template). Then I multiply this value by the world size of a tile (16, in my system) and multiply that result by 1.41421356237 (The square root of 2). The reason for this is that, given a camera rotated 45 degrees around the vertical axis, you are viewing a tile not edge-wise, but diagonally. The distance between the corners of a square whose sides are 1x1 is, of course, the square root of 2.

With that calculated value, I multiply by the aspect ratio to get the orthographic height of the viewport, then use those values to set the ortho matrix. To illustrate, here is a bit of code to do just that in OpenGL:

void CIso::applyCamera()
{
    // Get the viewport resolution
    GLint viewport[4];
    glGetIntegerv( GL_VIEWPORT, viewport );

    // Set the projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    float aspect=(float)viewport[3]/(float)viewport[2];
    float screen_tiles=(float)viewport[2] / (float)tile_pixel_width_;
    float ortho_width=screen_tiles*(float)tile_world_size_*1.414213562373f;
    float ortho_height=ortho_width * aspect;
    glOrtho(-ortho_width/2, ortho_width/2, -ortho_height/2, ortho_height/2, 0.0f, 1000.0f);

    // Set the camera, pointed at (x_,z_), 30 degrees around X and 45 degrees around Z. 100 units (arbitrarily chosen, will probably need to be larger for a bigger map, to
    // Prevent far-plane clipping
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0,0,-100);
    glRotatef(30,1,0,0);
    glRotatef(-45.0f,0,1,0);
    glTranslatef(-x_,0, -z_);
}

That is working code from the demo that sets the projection matrix. The member tile_pixel_width_ of that class is set during initialization, to the width (in pixels) of a tile. 64, with the above template. The member tile_world_size_ is set by dividing the tile pixel width by 4, for a 16x16xH unit tile in the case of the above template.

2) Tile height. If you set up a 30 degree camera (the angle needed for a 2:1 projection) and view a cube, you will notice that the cube vertical faces are fore-shortened, due to the angle of the viewpoint. There needs to be careful consideration for the correspondence between the tile sprite and the cube primitive that is drawn. The height of the cube primitive needs to be scaled correctly to correspond with that foreshortening. This can take a little bit of trigonometry, but once you wrap your head around it it's not too complicated. In the case of my template tile above, the tile is a total of 64x48 pixels. (The rest is padding to bring it to 64x64.) That means 32 of the pixels are taken by the top of the tile, leaving 16 for the vertical faces. A bit of trig gives me the totally arbitrarily funky value of ~0.40824829f as the height to scale the unit-sized cube to in order to match the tile template.

With that in place, the rest is pretty simple. I put together a small demo in C++, using the latest RC of SFML 2.0. (I don't use XNA or C#.) But the code should be relatively explanatory. It's a quick, one-off demo meant to show the concepts, rather than a comprehensive prototype, but it works.

The framework:

main.cpp
Spoiler


app.h
Spoiler


app.cpp
Spoiler



And the isometric map code:

iso.h
Spoiler


iso.cpp
Spoiler


Here are the tiles used for the demo. (They should be in the root directory of the executable.)

dirttile.png
Posted Image

grasstile.png
Posted Image

leftramptile.png
Posted Image

rightramptile.png
Posted Image

The demo sets up a tiny sample map, then enters a loop in which the camera scrolls around endlessly in a circle. Nothing fancy. The tile geometry units are hard-coded, but in a real game I'd use loadable geometry in case I needed a weird tile of some sort. Here is a shot of the demo in action:

Posted Image

If you zoom in, you can see that the pixels are pretty close to pixel-perfect (or, at least they were on the source image; I really can't vouch for imgur's service after they're uploaded). Filtering artifacts might occur (very occasionally) but if you pad your sprites correctly, even on the rare times they happen, they are not that noticeable.

I didn't have time at work to hack in mob sprites, but depth testing IS working correctly, and the sprites would be correctly clipped. (You can tell depth buffering is working, as the tiles are drawn from front (top, nearest corner) to back (bottom, far corner), and yet the back tiles are correctly clipped by the front tiles.


#4968844 Dynamic objects in Isometric Map Drawing algorythms

Posted by JTippetts on 12 August 2012 - 03:44 PM

The question isn't about different art. Like I said, you can continue to use the same exact art you have. You were on the right track when you suggested to use nearest filtering and project it onto an ortho cube. That is exactly right. And yes, you can set up your mathematical abstraction so that the 3D version will look identical to the 2D. Here is a tile I made just now:

Posted Image

I made a couple versions of it (lighter and darker). Then I took that tile and (1) hand-arranged several copies as if I were a 2D renderer, stacking blocks back to front and positioning the sprites just so to line up. Then (2) took the tile, mapped it onto a cube in Blender, set up an orthographic projection and a material with no texture filtering, then built a stack of blocks identical to the first. Can you tell me which one is the 2D version and which one is the 3D version?

Posted Image

I sure can't tell, and neither can the Gimp. Doing a subtraction operation between the two as layers results in a perfectly black bitmap. The two versions are pixel-perfect identical. However, the advantage of the 3D rendering (which is on the right, in case you were wondering) is this little goodie right here:

Posted Image

See that little beastie? That's the depth map of the 3D version. If this were in-game, that depth buffer would be used to clip incoming fragments. You can very easily draw your entire map, then go back and draw all your mobs, and the z-buffering will ensure that pixels clip correctly. It's so easy, I honest to God wonder why in this day and age anyone would ever bother with trying to rehash all the old techniques.

If you don't want to use the hardware for whatever reason, that's fine. You can implement your own z-buffer, build a simple 3D abstraction for your map layer, and do the depth testing yourself. It's relatively simple (though it is easier to just let the hardware handle it for you; that's what it's designed for). But revisiting 20+ years of old 2D techniques, hoping that things will turn out better this time, really isn't a solution. Nor is it even an interesting problem anymore. It's a very much solved problem, and all those sprite sorting and stacking tricks just get in the way of progress toward completing your game.

I don't mean to sound like some kind of crusty old know-it-all, but I have been doing isometric games now for almost 20 years. I've run the gamut of sprite sorting techniques, block tagging, you name it. Nothing beats the elegance of using the hardware correctly, in my opinion. To reinforce it, here are two images I've had kicking around for almost a decade now. They are from an old game I was working on, Golem. The first image was taken before I did a rewrite of the backend code to 3D. It was still using the original 2D code. The second image was from after the rewrite. You can see that visually, there is little difference. The game still looks the same. The differences were in the elimination of all the weird little edge cases that made the original 2D engine such a pain in the ass to work with. The code was made vastly simpler as well.

Posted Image

Posted Image

I was able to reuse all assets, I didn't have to make any artistic changes whatsoever. The game still looked 2D. The only difference was that the character could now walk through certain doorways or cross tile boundaries in certain ways, and not have to worry about being overdrawn in odd fashion by some other piece of the landscape.


#4968715 Dynamic objects in Isometric Map Drawing algorythms

Posted by JTippetts on 12 August 2012 - 08:22 AM

I fear you misunderstand exactly what "3D" is. 3D doesn't have to look terrible, good, ultra-realistic, or anything else. 3D is merely a set of mathematical transformations. An abstraction, if you will. You can accomplish in 3D exactly the visual appearance you have already established with your 2D, by using an orthographic transformation and your 2D graphics view-projected onto properly-shaped primitives. But it will have the added bonus of a Z-buffer which will greatly aid your efforts of populating the map with dynamic objects. It will certainly be at least an order of magnitude easier than hacking together some kind of god-awful convoluted draw algorithm, as you seem to be working on. On a platform with the proper hardware support, I can think of absolutely zero valid reasons to be doing something like this the "old school" way. This is coming from someone who has written many isometric games and prototypes, who loves the isometric look and feel more than any other game.

It's all about utilizing the tools that modern technology provides to overcome some of the hurdles that people have been struggling with for years. Feel free to search the extensive back-history of the old Isometric Games forum here at gamedev.net. You will find countless posts on this very issue. What you won't find is a solution that is simple and elegant.... unless you go 3D.


#4963979 Animation and Art for 2D RPG

Posted by JTippetts on 28 July 2012 - 09:08 AM

It is possible to construct your spritesheets in such a way that they can be layered together in paper-doll like fashion. Diablo 2 did this. However, one thing to be aware of: in 2D, using pre-drawn sprites, this is not a trivial task. It is made even more complicated if you want isometric sprites. For example, take this turtle dude holding a wooden axe:

Posted Image

To draw this, you could draw the turtle dude first, then the axe on top:

Posted Image + Posted Image

However, you can't simply specify the stacking order once per character, because depending on the orientation of the character, the rendering order might instead be Axe first, Turtledude second, as in this orientation:

Posted Image

So it requires being able to specify a drawing order per frame (since even within a given animation sequence for a given facing direction, the drawing order could possibly change) and that requires a quite time-consuming process. It also requires specially constructed tools for processing the sprite pieces. In the above example, the axe sprite is not exactly correct. I performed the render as turtle first and axe second, each piece separate, but if you draw the frame as specified, then the axe will simply be drawn over the top of the turtle sprite and the part of the handle that should be obscured by the turtle's hand where the hand grips the shaft will not be obscured:

Posted Image

So you need to write a tool of some sort that can take the separate renders and process them so that the pieces fit correctly. One possible way of doing this is to export a depth map during your rendering pipeline and use the depth map to determine which pixels of the axe will be visible when drawn, and snip out the hidden ones. You could also conceivably use this depth map to determine the drawing order as well.

It gets even more complicated when you add more pieces of equipment. Whether you hand-draw things or render them, it still means you are in for a great deal of grunt work. The task will be made vastly simpler if you build the proper tools to help with all of this. Doing it all by hand, for hundreds of frames per character, dozens of characters per game, is just not feasible. You need tools to help reduce the workload.

Another problem with this system is the fact that texture memory usage can explode. You will want to pack your sprites as tightly as possible, and even still you are going to eat a chunk of memory. Consider the above turtle dude. Say you have 8 facing directions for an isometric; and 8 is considered low, especially for a player character. 16 or even 32 would be more appropriate, at least for a player character. And say a typical animation set for the character includes 50 frames total. This includes all animations for walking, idling, attacking, casting spells, etc... 50 is a good guess, though more detailed characters would want more. 50 frames x 8 directions equals 400 frames. In a naive non-packing implementation, 400 frames @ 128x128x4 is equal to 25MB of data. And that's just for the base sprite. In a non-packing arrangement (ie, the sprite rectangles are not snipped and packed but are left as full frames) every piece of equipment you add will add another 25MB to the total. The axe, for example, would need to be rendered 50 frames in 8 facing directions just as the turtledude base sprite. If he's wearing a hat, that's more renders. It adds up quickly.

Proper sprite packing can mitigate this quite a bit. The axe sprite above is mostly empty space. Your animation tool can "snip" these sprites and pack them in a more dense arrangement to save space. My personal experiments show an average size reduction of 1/3 to 1/5, so the axe sprite set might take up 5 to 8MB instead of the full 25MB. Some very small sprites will be even better reduction, but even so it adds up very fast. It is a lot of texture memory to juggle.

Once again, the proper tool will make the job easier. I don't personally know of any pre-existing solution (outside of my own personal tools, which I don't make available) so you'll probably have to build your own.

Now, here is a little secret. There is one change that you can make, that will very nearly make all of this complexity go away. No fiddling with drawing orders, no farting around with sprite editing and processing and rectangle packing and worrying about exploding texture memory usage. And that change is this: use fully 3D characters.

It is not too hard to use full 3D characters, even if the backgrounds and landscapes remain 2D. You do have to build an abstraction where the landscape is drawn in a 3D fashion, but you can continue to use pre-rendered 2D sprites for the landscape pieces. But if you draw your character as a full 3D character, all of the above complexity goes away. Drawing order? Just draw the pieces, and let the Z buffer sort it out. Texture memory usage? A single small texture per weapon or equipment piece, plus a small geometry buffer. No extra memory required for the various facing directions. You simply attach 3D models to a character skeleton, and let the rendering process handle making it all look right.

I highly recommend that you go the 3D route if you plan on any sort of complexity in your equipment system. On modern hardware, there really is no reason not to. The results can be very nearly as good as pre-rendered (although you do miss out on some neat tricks, such as anti-aliased sprites with smooth blending against the background) but a good shader and post-processing chain can mitigate a lot of what you miss out on from the switch away from pre-rendered sprites. You avoid the necessity of having to write a tool to process sprites, you save yourself a ton of time from pre-rendering all the various equipment models and tweaking drawing orders, etc... This is the reason that most existing 2D games that use paper doll systems either stick to very simple characters, or drastically limit the equipment choices available. (Look again at Diablo 2. There were only a very few actual different armor and weapon models, compared to the number of weapons and armors actually available to you.) Compare that to the number of equipment options in, say, World of Warcraft. (There are literally hundreds, if not thousands, of equipment models per character base type.) You just couldn't manage that level of complexity using traditional 2D sprite methods; the memory usage alone would eat most graphics cards alive.


#4959266 Reset gameTime.TotalGameTime.Seconds to zero

Posted by JTippetts on 15 July 2012 - 08:43 AM

What you could do is in MultipleEnemies(), set a member called lastTime=0. Then in Update() you would set time=gameTime.TotalGameTime.Seconds, then set a local variable deltaTime=time-lastTime. This would give the amount of time since the last time through the Update loop. If this value is greater than 1 second, increment SpawnNextEnemy. At the end of Update set lastTime=time. This way, you don't have to worry about setting your main game timer to 0. You are always keeping track of the last time Update was called, and using the elapsed time to determine whether to spawn an enemy.


#4959061 What is your technique for level designing?

Posted by JTippetts on 14 July 2012 - 07:48 AM

You might read through some of the stuff at worldofleveldesign.com for some ideas. It's very UDK-centric, but there is still a lot of good stuff there.


#4958988 How to build the world most efficiently if it's going to be like

Posted by JTippetts on 13 July 2012 - 09:07 PM

The problem with just using Blender to build your world is that a good world is more than just geometry and textures. It also includes scripted events, encounters, dynamic objects, etc... which are harder to handle. You could hack something using Blender. Maybe something like creating named Empty objects at event locations, and naming the Empty to be parsed by the engine to know what to place. It's hackish and inflexible and doing anything non-trivial that way would be a monster. In the long run, your time would have been much better spent on the level editor.

You could also just do that stuff outside Blender, hand-writing a bunch of scripts and placing things manually. It would take a lot of iteration, a lot of loading up in the game and playing for a few seconds, then reloading, etc... Still would be a lot of work.

You would have to just estimate how long it would take to do things by hand like that, vs how long it would take to build a proper editor and build the map there. That is the only way you'll know if you're actually saving any time by not building the editor. I suspect that if the map is of any non-trivial size, it would actually be much faster to build the editor and amortize the time spent on that as savings throughout the remainder of the process.


#4957289 looks like I remember less than I thought... *smiles*

Posted by JTippetts on 09 July 2012 - 08:42 AM

Also, I personally advise you to steer clear of C-style strings.


#include <iostream>
#include <string>
using namespace std;
int main()
{
    const int CLASS = 6;
    const int NAME = 15;
    std::string ships[CLASS];
    ships[1] = "Frigate";
    ships[2] = "Destroyer";
    ships[3] = "Cruiser";
    ships[4] = "Battleshop";
    ships[5] = "Dreadnaught";

    cout << "Ship types as follows:\n";
    cout << ships[1] << endl
                 << ships[2] << endl
                 << ships[3] << endl
                 << ships[4] << endl
                 << ships[5] << endl;
    cin.get();
    return 0;
}

When you throw C-strings into the mix, you have to worry about buffer overruns and other flaws. For example, what if you add another ship type "Heavy Battlecruiser"? I'll tell you what happens. *STOMP* *STOMP* goes your memory, since the elements can only hold 15 chars. Such considerations disappear if you use a proper string class.


#4957282 looks like I remember less than I thought... *smiles*

Posted by JTippetts on 09 July 2012 - 08:32 AM

It's because ships[] is a multi-dimensional array of char, not string, but you're trying to assign strings to the elements.


#4957040 Can someone point me to the newb corner? =+)

Posted by JTippetts on 08 July 2012 - 03:55 PM

You do realize that your torrent joke is likely to go over a little flat, considering that some of the folks who frequent these forums are authors of those books, right?

That being said, you might try clicking the various tabs at the top menu, and switching over to Resources. The Community link has a list of books as well.


#4953947 [solved] attempt to index global 'location' (a nil value) Help please...

Posted by JTippetts on 29 June 2012 - 07:18 AM

Look at the order in which you construct your rooms. First, you have this:


foresta = {name = "foresta", Desc = "you look around and you are in a dark forest, something is rustling North of you"}
foresta.exit = { N = forestsnork, E = foresta, S = foresta, W = clearing}

Then further down you have this:


clearing = {name = "clearing", Desc = "you enter a small clearing, and find a small health potion"}
clearing.exit = {N = foresta, E = foresta, S = foresta, W = foresta}

When the foresta room is constructed, the exits are set to forestsnork, foresta, foresta, and clearing. The problem is, forestsnork and clearing haven't been created yet. Lua is interpreted. That means that objects will be constructed in the order they are encountered during execution. Since forestsnork and clearing don't exist yet when you try to set them to entries in foresta.exit, then instead the N and W values of foresta.exit are set to nil. Then if you move N or W, the location variable will be set to nil and the next time you try to access any members of location, you are trying to access members of a nil table and Lua will error.

Try constructing all your room tables first, then setting the various exit members once all rooms are constructed.




PARTNERS