// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation; either version 2 of the License, or
//   (at your option) any later version.
//  
//   This program is distributed in the hope that it will be useful, but
//   WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//   General Public License for more details.
// 
//   You should have received a copy of the GNU General Public License
//   along with this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include "../hacks/winhacks.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
using namespace std;

#include "../console/console.h"
using namespace Console;

#include <SDL.h>
#include <GL/glew.h>
#include <GL/gl.h>

#include <zlib.h>

#include <sys/stat.h>

#include "../texturemanager/texturemanager.h"
#include "../texturemanager/texture.h"
#include "../malfont/fontmanager.h"
#include "../filesystem/filesystem.h"
#include "../tinyxml/xmlstuff.h"
#include "../string/string.h"
#include "../uijunk/widget.h"

#include "quadtree.h"
#include "world.h"
#include "entity.h"
#include "scriptentity.h"
#include "terrainblock.h"
#include "editorui.h"
#include "scenerychunk.h"
#include "tileent.h"
#include "../lua/luatableserializer.h"
#include "renderer.h"
#include "engineui.h"
#include "input.h"

World *activeWorld = NULL;

#define FADE_MAX_TIME 120

/*const char World::layerNames[World::maxLayers][256] = {
	"Layer 0 (Static BG)",
	"Layer 1 (Distant BG)",
	"Layer 2 (Immediate BG)",
	"Layer 3 (Active stuff)",
	"Layer 4 (Immediate FG)",
	"Layer 5 (Very near FG)",
	"Layer 6 (HUD)"
    };*/

// --------------------------------------------------------------------
// Lua callbacks.
// --------------------------------------------------------------------

static bool checkNumLuaParams(lua_State *L, int expected, const std::string &funcName) {
    int numParams = lua_gettop(L);
    if(numParams != expected) {
        ostringstream str;
        str << "Wrong number of params for " << funcName << " (got " << numParams << ", expected " << expected << ")";
        luaL_error(L, str.str().c_str());
        return false;
    }
    return true;
}

#define CHECK_NUM_PARAMS(x) checkNumLuaParams(L, x, __FUNCTION__ )

int luaCB_writeConsole(lua_State *L) {
	int numParams = lua_gettop(L);
	
	EditorUI *editor = activeWorld->getEditor();
	
	for(int i = 0; i < numParams; i++) {
		const char *outText = lua_tostring(L, i - numParams);
		if(outText) {
			out("lua") << outText;
			
			if(editor) {
				editor->junkInterface.appendConsoleOutput(outText);
			}
		}
	}
	
	return 0;
}

int luaCB_registerSpawn(lua_State *L) {
    CHECK_NUM_PARAMS(1);

	activeWorld->addSpawn(lua_tostring(L, -1));
	return 0;
}

int luaCB_registerScenery(lua_State *L) {
	// Params: name, texture, x, y, x2, y2
	
    CHECK_NUM_PARAMS(6);
	
	activeWorld->addScenery(
		lua_tostring(L, -6),
		lua_tostring(L, -5),
		BoundingBox(
			lua_tonumber(L, -4),
			lua_tonumber(L, -3),
			lua_tonumber(L, -2),
			lua_tonumber(L, -1)));
	
	return 0;
}

int luaCB_getJoyAxis(lua_State *L) {

    static bool warnedDeprecated = false;

    if(!warnedDeprecated) {
        out("warning") << "Calling deprecated getJoyAxis() function" << endl;
        warnedDeprecated = true;
    }

    CHECK_NUM_PARAMS(1);

	lua_pushnumber(L, activeWorld->getControlAxisState(lua_tonumber(L, -1)));
	return 1;
}

int luaCB_getJoyButton(lua_State *L) {

    static bool warnedDeprecated = false;

    if(!warnedDeprecated) {
        out("warning") << "Calling deprecated getJoyButton() function" << endl;
        warnedDeprecated = true;
    }

    CHECK_NUM_PARAMS(1);

	lua_pushboolean(L, activeWorld->getControlButtonState(lua_tonumber(L, -1)));
	return 1;
}

int luaCB_getCameraPos(lua_State *L) {

    CHECK_NUM_PARAMS(0);

	lua_pushnumber(L, activeWorld->cameraLoc.x);
	lua_pushnumber(L, activeWorld->cameraLoc.y);
	return 2;
}

int luaCB_setCameraPos(lua_State *L) {

    CHECK_NUM_PARAMS(2);

    Vector2D newPos = Vector2D(lua_tonumber(L, -2), lua_tonumber(L, -1));
    Vector2D delta = newPos - activeWorld->cameraLoc;
	activeWorld->cameraLoc = newPos;

    Vector2D newMousePos = delta + activeWorld->getMousePos();
    activeWorld->setMousePos(newMousePos);

    // Skip everything else if we're being fancy and recursive.
    if(activeWorld->getIsInMouseEvent()) return 0;

	lua_getfield(L, LUA_GLOBALSINDEX, "handleMouseMotion");

	if(!lua_isnil(L, -1)) {

        lua_pushnumber(L, newMousePos.x);
        lua_pushnumber(L, newMousePos.y);
   
		if(lua_pcall(L, 2, 0, 0)) {
			out("error") << "Lua exploded because (setCameraPos mouse handling): " << lua_tostring(L, -1) << endl;
		}
	} else {
		lua_pop(L, 1);
	}

	return 0;
}

int luaCB_include(lua_State *L) {

    CHECK_NUM_PARAMS(1);

	std::string fileName = lua_tostring(L, -1);
	
	int length;
	char *data = FileSystem::loadFile(activeWorld->getRealFile(string("lua/") + fileName), &length, true);

    if(!data) {
        out("error") << "Could not load Lua file: " << fileName << endl;
        return 0;
    }
	
	if(luaL_loadstring(L, data)) {
		out("error") << "Lua error (compilation - " << fileName << "): " << lua_tostring(L, -1) << endl;
        delete[] data;
        return 0;
	}
	if(lua_pcall(L, 0, 0, 0)) {
		out("error") << "Lua error (include - " << fileName << "): " << lua_tostring(L, -1) << endl;
	}

	delete[] data;
	
	return 0;
}

int luaCB_getScreenSize(lua_State *L) {

    CHECK_NUM_PARAMS(0);

	lua_pushnumber(L, activeWorld->screenSize.x);
	lua_pushnumber(L, activeWorld->screenSize.y);
	return 2;
}

int luaCB_loadEntities(lua_State *L) {

    CHECK_NUM_PARAMS(1);
	
	// We need to reduce the stack to 0 because we're going to re-enter
	// some Lua code when we load stuff.
	string fileName = lua_tostring(L, -1);
	lua_pop(L, 1);
	
	activeWorld->loadEntities(fileName);
	return 0;
}

int luaCB_saveEntities(lua_State *L) {
	
	// Build up the list of exceptions.
	vector<string> exceptionList;
	
	while(lua_gettop(L) > 1) {
		exceptionList.push_back(lua_tostring(L, -1));
		lua_pop(L, 1);
	}
	
	// Lowest stack position should be the first parameter: the file name.
	string fileName;
	if(lua_tostring(L, -1)) {
		fileName = lua_tostring(L, -1);
	} else {
		out("error") << "Attempting to save without a file name (no save game name selected?)." << endl;
		return 0;
	}

	// Again, we need to make sure the stack is at 0.
	lua_pop(L, 1);
	
	activeWorld->saveEntities(fileName, exceptionList);
	return 0;
	
}

int luaCB_loadTiles(lua_State *L) {

    CHECK_NUM_PARAMS(1);

	activeWorld->loadTiles(lua_tostring(L, -1));
	return 0;
}

int luaCB_clearLayer(lua_State *L) {

    if(lua_gettop(L) < 1) {
        luaL_error(L, "clearLayer() needs at least the layer number to clear as a parameter");
    }

	int numParams = lua_gettop(L);
	int layerNum = lua_tonumber(L, -numParams);
	
	std::map<string, bool> exceptionList;
	
	numParams--;
	
	while(numParams > 0) {
		exceptionList[lua_tostring(L, -numParams)] = true;
		numParams--;
	}
	
	lua_pop(L, numParams);
	
	for(unsigned int i = 0; i < activeWorld->entities.size(); i++) {
		if(activeWorld->entities[i]->getLayer() == layerNum && !exceptionList[activeWorld->entities[i]->getName()]) {
            if(!activeWorld->entities[i]->getIsPermanent()) {
                activeWorld->entities[i]->die();
            }
        }
	}
	for(unsigned int i = 0; i < activeWorld->newEntities.size(); i++) {
		if(activeWorld->newEntities[i]->getLayer() == layerNum && !exceptionList[activeWorld->newEntities[i]->getName()]) {
			if(!activeWorld->newEntities[i]->getIsPermanent()) {
				activeWorld->newEntities[i]->die();
			}
		}
	}
	
	return 0;
}

int luaCB_clearTiles(lua_State *L) {

    CHECK_NUM_PARAMS(0);
	
	activeWorld->clearAllTiles();
	
	return 0;
}

int luaCB_getTile(lua_State *L) {

    CHECK_NUM_PARAMS(3);
	
	int x = lua_tonumber(L, -3);
	int y = lua_tonumber(L, -2);
	int layer = lua_tonumber(L, -1);
	
	lua_pushnumber(L, activeWorld->getTile(x, y, layer));
	return 1;
}

int luaCB_setTile(lua_State *L) {

    CHECK_NUM_PARAMS(4);

	int x = lua_tonumber(L, -4);
	int y = lua_tonumber(L, -3);
	int layer = lua_tonumber(L, -2);
	int tile = lua_tonumber(L, -1);
	
	activeWorld->setTile(x, y, layer, tile);
	return 1;
}

int luaCB_worldSpaceToTileSpace(lua_State *L) {

    CHECK_NUM_PARAMS(3);

	float x = lua_tonumber(L, -3);
	float y = lua_tonumber(L, -2);
	int layer = lua_tonumber(L, -1);
	
	float tileSize = activeWorld->getTileSize(layer);
	
	float rx = x / tileSize;
	if(rx < 0) rx--;
	float ry = y / tileSize;
	if(ry < 0) ry--;
	
	lua_pushnumber(L, int(rx));
	lua_pushnumber(L, int(ry));
	
	return 2;
}

int luaCB_tileSpaceToWorldSpace(lua_State *L) {

    CHECK_NUM_PARAMS(3);
	
	float x = lua_tonumber(L, -3);
	float y = lua_tonumber(L, -2);
	int layer = lua_tonumber(L, -1);
	
	float tileSize = activeWorld->getTileSize(layer);
	
	lua_pushnumber(L, x * tileSize);
	lua_pushnumber(L, y * tileSize);
	
	return 2;
	
}

int luaCB_getBoxTouchesWorld(lua_State *L) {
	
	CHECK_NUM_PARAMS(5);

	int layer = lua_tonumber(L, -5);
	
	BoundingBox bb(
		lua_tonumber(L, -4),
		lua_tonumber(L, -3),
		lua_tonumber(L, -2),
		lua_tonumber(L, -1));
	
	vector<Entity*> ents;
	activeWorld->getTileEntities(bb, layer, ents);
	
	for(unsigned int i = 0; i < ents.size(); i++) {
		if(ents[i]->getBoundingBox().overlap(bb)) {
			lua_pushboolean(L, true);
			return 1;
		}
	}
	
	ents.clear();
	activeWorld->getQuadTree(layer)->getEntsTouchingBox(bb, ents);
	
	for(unsigned int i = 0; i < ents.size(); i++) {
		TerrainBlock *tb = dynamic_cast<TerrainBlock*>(ents[i]);
		if(tb) {
			Polygon boxPoly = bb.getPoly();
			Polygon tbPoly = tb->getCollisionArea();
			if( separatingAxisTest(tbPoly, boxPoly) &&
				separatingAxisTest(tbPoly, boxPoly)) {
				
				lua_pushboolean(L, true);
				return 1;
			}
		}
	}
	
	lua_pushboolean(L, false);
	return 1;
}

int luaCB_getEntsTouchingBox(lua_State *L) {
	
    CHECK_NUM_PARAMS(5);

	int layer = lua_tonumber(L, -5);
	
	BoundingBox bb(
		lua_tonumber(L, -4),
		lua_tonumber(L, -3),
		lua_tonumber(L, -2),
		lua_tonumber(L, -1));
	
	vector<Entity*> ents;
	activeWorld->getQuadTree(layer)->getEntsTouchingBox(bb, ents);

	lua_newtable(L);
	
	int entIndex = 1;
	
	for(unsigned int i = 0; i < ents.size(); i++) {
		ScriptEntity *se = dynamic_cast<ScriptEntity*>(ents[i]);

		if(se) {
			lua_pushnumber(L, entIndex++);
			lua_pushstring(L, se->getName().c_str());
			lua_settable(L, -3);
		}
	}
	
	return 1;
}

int luaCB_playSound(lua_State *L) {

    int params = lua_gettop(L);

    // TODO: Replace this assert with a normal check.
	assert(params == 1 || params == 2);
	
    string name  = (params == 1) ? lua_tostring(L, -1) : lua_tostring(L, -2);
    float volume = (params == 1) ? 1 : lua_tonumber(L, -1); 

	string fixedName = activeWorld->getRealFile(string("sounds/") + fixDeprecatedPath(name));
	
    activeWorld->soundResourceManager.queueSound(fixedName, volume);
	
	return 0;
}

int luaCB_preloadSound(lua_State *L) {

	CHECK_NUM_PARAMS(1);
	
    // FIXME
    out("error") << "preloadSound() currently doesn't do anything." << endl;
	
	return 0;
}

int luaCB_startMusic(lua_State *L) {

	CHECK_NUM_PARAMS(1);
	
    activeWorld->soundResourceManager.queueMusic(activeWorld->getRealFile(string("music/") + lua_tostring(L, -1)));
	
	return 0;
}

int luaCB_stopMusic(lua_State *L) {

	CHECK_NUM_PARAMS(0);
	
    activeWorld->soundResourceManager.queueMusic("");
	
	return 0;
}

int luaCB_getGravity(lua_State *L) {

	CHECK_NUM_PARAMS(0);

	lua_pushnumber(L, activeWorld->gravity);
	
	return 1;
}

int luaCB_setGravity(lua_State *L) {

	CHECK_NUM_PARAMS(1);
	
	activeWorld->gravity = lua_tonumber(L, -1);
	
	return 0;
}

int luaCB_saveTable(lua_State *L) {

    CHECK_NUM_PARAMS(2);

	string realFileName = activeWorld->getRealFile(lua_tostring(L, -2), true);
	
	FileSystem::makePathForSave(realFileName);
	
	LuaTableSerializer lts;
	
	TiXmlElement *xml = lts.tableToXml(L);
	
	// Make a new document.
	TiXmlDocument *doc = new TiXmlDocument();
	
	doc->LinkEndChild(xml);

	// Make a printer and give it the data.
	TiXmlPrinter printer;
	printer.SetIndent("\t");
	doc->Accept(&printer);
	delete doc;
	
	// Write it all out.
	// TODO: Check for opern/writing error?
	ofstream outFile;
	outFile.open(realFileName.c_str());
	outFile << printer.CStr();
	outFile.close();

	
	return 0;
}

int luaCB_loadTable(lua_State *L) {

    CHECK_NUM_PARAMS(1);

	string realName = activeWorld->getRealFile(lua_tostring(L, -1));
	
	TiXmlDocument *doc = loadXml(realName.c_str());
	if(doc) {
		TiXmlElement *luaXml = doc->FirstChildElement("luaState");
		if(luaXml) {
			LuaTableSerializer lts;
			lts.tableFromXml(L, luaXml);
		} else {
			lua_pushnil(L);
		}
	} else {
		lua_pushnil(L);
	}
	
	delete doc;
	
	return 1;
}

int luaCB_setSaveName(lua_State *L) {
	
	CHECK_NUM_PARAMS(1);

	const char *name = lua_tostring(L, -1);
	
	if(name) {
		activeWorld->setSaveName(lua_tostring(L, -1));
	} else {
		activeWorld->setSaveName("");
	}
	
	return 0;
}

int luaCB_getSaveName(lua_State *L) {

    CHECK_NUM_PARAMS(0);

	lua_pushstring(L, activeWorld->getSaveName().c_str());
	
	return 1;
}

/*
static bool compareString(const std::string &str1, const std::string &str2) {
	for(unsigned int i = 0; i < str1.size(); i++) {
		if(i > str2.size()) return false;
		if(str1[i] < str2[i]) return false;
		if(str2[i] > str2[i]) return true;
	}
	return false;
}
*/

int luaCB_getSaveList(lua_State *L) {

    CHECK_NUM_PARAMS(0);

	lua_newtable(L);
	
	vector<string> names;
	FileSystem::getSubdirectories(string("saves/") + activeWorld->getGameName(), names);
	
	sort(names.begin(), names.end()); //, compareString);

	for(unsigned int i = 0; i < names.size(); i++) {
		lua_pushnumber(L, i + 1);
		lua_pushstring(L, names[i].c_str());
		lua_settable(L, -3);
	}
	
	return 1;
}

int luaCB_setExclusiveUpdatesThisFrame(lua_State *L) {

	vector<string> names;
	
	while(lua_gettop(L) > 0) {
		names.push_back(lua_tostring(L, -1));
		lua_pop(L, 1);
	}
	
	activeWorld->setExclusiveUpdatesThisFrame(names);
	
	return 1;
}

int luaCB_unnameDeadEntities(lua_State *L) {

	CHECK_NUM_PARAMS(0);

	activeWorld->unnameDeadEntities();
	
	return 0;
}

// Debug-only Lua callbacks for use in the console.

int luaCB_reloadTextures(lua_State *L) {
	
	CHECK_NUM_PARAMS(0);
	
	TextureManager *manager = TextureManager::get();
	manager->cleanOldTextures(0);
	manager->cleanOldTextures(0);
	
	return 0;
}

int luaCB_getRealFile(lua_State *L) {
	
	CHECK_NUM_PARAMS(1);

	string fileName = activeWorld->getRealFile(lua_tostring(L, -1));
	
	lua_pushstring(L, fileName.c_str());
	
	return 1;
	
}

int luaCB_setSubtractiveFade(lua_State *L) {

	CHECK_NUM_PARAMS(3);

	activeWorld->setSubtractiveFade(
		lua_tonumber(L, -3),
		lua_tonumber(L, -2),
		lua_tonumber(L, -1));

	return 0;
}

int luaCB_setAdditiveFade(lua_State *L) {

	CHECK_NUM_PARAMS(3);

	activeWorld->setAdditiveFade(
		lua_tonumber(L, -3),
		lua_tonumber(L, -2),
		lua_tonumber(L, -1));

	return 0;
}

int luaCB_setControlName(lua_State *L) {
    
	CHECK_NUM_PARAMS(2);

    engineUISetControlActionName(lua_tonumber(L, -2), lua_tostring(L, -1));

    return 0;
}

int luaCB_getControlValue(lua_State *L) {
    
	CHECK_NUM_PARAMS(1);
    
    int val = inputGetAxisState(lua_tonumber(L, -1));

    lua_pushnumber(L, val);

    return 1;

}

int luaCB_getControlBoolValue(lua_State *L) {
    
	CHECK_NUM_PARAMS(1);

    int val = inputGetAxisState(lua_tonumber(L, -1));

    lua_pushboolean(L, val > 8192);

    return 1;

}

int luaCB_getMousePos(lua_State *L) {

	CHECK_NUM_PARAMS(0);

    Vector2D mousePos = activeWorld->getMousePos();

    lua_pushnumber(L, mousePos.x);
    lua_pushnumber(L, mousePos.y);

    return 2;
}

int luaCB_getMouseButton(lua_State *L) {
    
	CHECK_NUM_PARAMS(1);

    bool mouseButton = activeWorld->getMouseButtonState(lua_tonumber(L, -1));

    lua_pushboolean(L, mouseButton);

    return 1;
}

int luaCB_getNumLayers(lua_State *L) {

    CHECK_NUM_PARAMS(0);

    int ret = activeWorld->getNumLayers();

    lua_pushnumber(L, ret);

    return 1;
}

int luaCB_getLayerByName(lua_State *L) {

    CHECK_NUM_PARAMS(1);

    string layerName = lua_tostring(L, -1);
    int ret = -1;

    for(int i = 0; i < activeWorld->getNumLayers(); i++) {
        
        if(layerName == activeWorld->getLayerName(i)) {
            ret = i;
            break;
        }

    }
    
    lua_pushnumber(L, ret);

    return 1;

}

// --------------------------------------------------------------------
// World functions
// --------------------------------------------------------------------

#define WORLD_QUADTREE_DEPTH 3

void World::setupDefaultLayers(void) {
    
    // Delete all the existing stuff.
    for(unsigned int i = 0; i < layers.size(); i++) {
        delete layers[i];
    }
    layers.clear();

    // Create all the default layers.
	for(int i = 0; i < maxLayers; i++) {

        ostringstream layerName;
        layerName << "Layer " << i;

        Layer *layer = new Layer();
        layer->name = layerName.str();

        layer->tilePane.layer = i;
        layer->tilePane.setWorld(this);

        switch(i) {	
        case 0:
            layer->cameraPosScale = 0;
            break;
        case 1:
            layer->cameraPosScale = 0.25;
            break;
        case 2:
        case 3:
        case 4:
            layer->fullbrightLightsEnabled = true;
            layer->cameraPosScale = 1;
            break;
        case 5:
            layer->cameraPosScale = 2;
            break;
        case 6: // HUD
            layer->cameraPosScale = 0;
            break;
        default:
            layer->cameraPosScale = 1;
            break;
        }

        layers.push_back(layer);
	}

    // Make sure the fields are up to date in the editor.
    if(editor) {
        editor->junkInterface.updateLayerList(this);
    }

}

World::World(const std::string &gameName) : soundResourceManager(this) {

	this->gameName = gameName;
	
	widgetSetDefaultStr("WidgetText.fontName", getRealFile("fonts/font1.tga"));

    editor = NULL;
    setupDefaultLayers();

	// TODO: Load from config or game settings.
	int screenWidth = 1024;
	int screenHeight = 768;
	
	renderer = new Renderer();
	renderer->init(screenWidth, screenHeight);
	
	//joystick = SDL_JoystickOpen(0);
    joystick = NULL;
	if(!joystick) {
		out("system") << "No joystick initialized (" << SDL_NumJoysticks() << " detected)" << endl;
	} else {
		out("system") << "Joystick detected: " << SDL_JoystickName(0) << endl;
	}
	handlingMouseEventNow = false;

	editor = NULL;
	unnamedCounter = 0;
    setDefaultAmbient();
	screenSize = Vector2D(screenWidth, screenHeight);
	zoomLevel = 1;
	gravity = 2;
	
    lightingEnabled = true;
    retroEffects = true;

	// Get the main texture manager.
	textureManager = TextureManager::get();
	
	// And the font manager.
	fontManager = FontManager::get();
	
	frameCounter = 0;

    // Initial mouse crap.
    lastMousePos = Vector2D(0, 0);
    for(int i = 0; i < 5; i++) {
        mouseButtonStates[i] = false;
    }	

	// Create the lua_State;
	L = luaL_newstate();
	luaL_openlibs(L);
	
	// Create the table to remember all the entities by name for easy
	// access later. (Also stores references to them so we don't worry
	// about them getting GCed.)
	lua_newtable(L);
	lua_setglobal(L, "entitiesByName");
	
	// Create the base entity class in Lua.
	setupLuaBaseClass(L);
	
	// TODO: Register global Lua bindings here.
	// x include()
	// x global update
	//   get time
	//   save
	// x load
	
	lua_register(L, "writeConsole", luaCB_writeConsole);
	lua_register(L, "registerSpawn", luaCB_registerSpawn);
	//lua_register(L, "getUnusedNumber", luaCB_getUnusedNumber);
	lua_register(L, "registerScenery", luaCB_registerScenery);
	
	// Control stuff
	lua_register(L, "getJoyAxis", luaCB_getJoyAxis);
	lua_register(L, "getJoyButton", luaCB_getJoyButton);
	
	// Camera control
	lua_register(L, "getCameraPos", luaCB_getCameraPos);
	lua_register(L, "setCameraPos", luaCB_setCameraPos);
	
	lua_register(L, "getScreenSize", luaCB_getScreenSize);
	
	lua_register(L, "include", luaCB_include);
	
	lua_register(L, "loadTiles", luaCB_loadTiles);
	lua_register(L, "loadEntities", luaCB_loadEntities);
	lua_register(L, "saveEntities", luaCB_saveEntities);
	
	lua_register(L, "clearLayer", luaCB_clearLayer);
	lua_register(L, "clearTiles", luaCB_clearTiles);
	
	lua_register(L, "setTile", luaCB_setTile);
	lua_register(L, "getTile", luaCB_getTile);
	
	lua_register(L, "worldSpaceToTileSpace", luaCB_worldSpaceToTileSpace);
	lua_register(L, "tileSpaceToWorldSpace", luaCB_tileSpaceToWorldSpace);
	
	lua_register(L, "getBoxTouchesWorld", luaCB_getBoxTouchesWorld);
	lua_register(L, "getEntsTouchingBox", luaCB_getEntsTouchingBox);
	
	lua_register(L, "playSound", luaCB_playSound);
	lua_register(L, "preloadSound", luaCB_playSound);
	
	lua_register(L, "startMusic", luaCB_startMusic);
	lua_register(L, "stopMusic", luaCB_stopMusic);
	
	lua_register(L, "getGravity", luaCB_getGravity);
	lua_register(L, "setGravity", luaCB_setGravity);
	
	lua_register(L, "saveTable", luaCB_saveTable);
	lua_register(L, "loadTable", luaCB_loadTable);
	
	lua_register(L, "setSaveName", luaCB_setSaveName);
	lua_register(L, "getSaveName", luaCB_getSaveName);
	
	lua_register(L, "getSaveList", luaCB_getSaveList);
	
	lua_register(L, "setExclusiveUpdatesThisFrame", luaCB_setExclusiveUpdatesThisFrame);
	lua_register(L, "unnameDeadEntities", luaCB_unnameDeadEntities);
	
	lua_register(L, "reloadTextures", luaCB_reloadTextures);
	
	lua_register(L, "getRealFile", luaCB_getRealFile);	

	lua_register(L, "setSubtractiveFade", luaCB_setSubtractiveFade);
	lua_register(L, "setAdditiveFade", luaCB_setAdditiveFade);
	
    lua_register(L, "setControlName", luaCB_setControlName);
    lua_register(L, "getControlValue", luaCB_getControlValue);
    lua_register(L, "getControlBoolValue", luaCB_getControlBoolValue);

    lua_register(L, "getMousePos", luaCB_getMousePos);
    lua_register(L, "getMouseButton", luaCB_getMouseButton);

    lua_register(L, "getNumLayers", luaCB_getNumLayers);
    lua_register(L, "getLayerByName", luaCB_getLayerByName);

	// UIJunk lib setup.
	widgetInitLua(L);
	
	// Fade out/in stuff.
	fadeCountdown = 0;
	for(int i = 0; i < 3; i++) {
		additiveFade[i] = 0;
		subtractiveFade[i] = 1;
	}

    if(gameName.size()) {
        runInitLua();
    }
}

World::~World(void) {
	clearEntities();
	
	lua_close(L);
	if(joystick) SDL_JoystickClose(joystick);
	
	renderer->shutdown();

	for(int i = 0; i < int(layers.size()); i++) {
		layers[i]->tilePane.clear();
	}

    killLayerBuffers();

    for(unsigned int i = 0; i < layers.size(); i++) {
        delete layers[i];
    }

	delete renderer;
}

void World::runInitLua(void) {
	// Initial run of the main Lua script.
	activeWorld = this;
	string startScript = getRealFile("lua/main.lua");
	if(startScript.size()) {
		
		int length;
		char *data = FileSystem::loadFile(startScript, &length, true);
		
        if(data && length > 0) {

            if(luaL_loadstring(L, data)) {
                out("error") << "Lua exploded because (compilation): " << lua_tostring(L, -1) << endl;
            } else if(lua_pcall(L, 0, 0, 0)) {
                out("error") << "Lua exploded because (initialization): " << lua_tostring(L, -1) << endl;
            }
		
            delete[] data;

        } else {

            out("error") << "Error loading lua/main.lua for this game (" << gameName << ")." << endl;

        }

	} else {
		out("error") << "No lua/main.lua found for this game (" << gameName << ")." << endl;
	}
}

void World::clearEntities(void) {
	vector<Entity*> saveEntities;
	vector<Entity*> saveNewEntities;
	
	for(unsigned int i = 0; i < entities.size(); i++) {
		if(entities[i]->getIsPermanent()) {
			saveEntities.push_back(entities[i]);
		} else {
			delete entities[i];
		}
	}
	
	for(unsigned int i = 0; i < newEntities.size(); i++) {
		if(newEntities[i]->getIsPermanent()) {
			saveNewEntities.push_back(newEntities[i]);
		} else {
			delete newEntities[i];
		}
	}
	
	entities.clear();
	entities.insert(entities.end(), saveEntities.begin(), saveEntities.end());
	newEntities.clear();
	newEntities.insert(newEntities.end(), saveNewEntities.begin(), saveNewEntities.end());
	
	entitiesByName.clear();
	for(unsigned int i = 0; i < entities.size(); i++) {
		entitiesByName[entities[i]->getName()] = entities[i];
	}
	for(unsigned int i = 0; i < newEntities.size(); i++) {
		entitiesByName[newEntities[i]->getName()] = newEntities[i];
	}
}

std::string World::getUnusedName(const std::string &prefix) {
	string outStr;
	do {
		ostringstream str;
		str << prefix << unnamedCounter++;
		outStr = str.str();
	} while(entitiesByName[outStr] != NULL);
	
	return outStr;
}

/*
int World::getUnusedNumber(void) {
	return unnamedCounter++;
}
*/

void World::setLighting(bool lighting) {
    lightingEnabled = lighting;
}

bool World::getLighting(void) {
    return lightingEnabled;
}

static int getPow2(int x) {
    int y = 256; // I don't think the screen will be smaller than this...
    while(y < x) {
        y <<= 1;
    }
    return y;
}

void World::killLayerBuffers(void) {

   	for(int layer = 0; layer < int(layers.size()); layer++) {


    }
}

static void saveAsTexture(unsigned int layerTex, int w, int h) {
    
    // FIXME: This is horrendously slow.

    glBindTexture(GL_TEXTURE_2D, layerTex);

    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, w, h, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glBindTexture(GL_TEXTURE_2D, 0);

}

// Light layer resoultion.
//const int lightLayerScaleDenominator = 128;
//const float lightLayerScale = 1.0 / float(lightLayerScaleDenominator);

// FIXME: Nevermind, that was a shitty idea.
const float lightLayerScale = 1;

// FIXME: Remove this, and the corresponding glPopMatrix() calls.
static void pushLightLayerScale(float xOffset, float yOffset) {

    glPushMatrix();

    // Offset so stuff moves with the camera.
    glTranslatef(xOffset, yOffset, 0);

    glScalef(lightLayerScale, lightLayerScale, lightLayerScale);

    glTranslatef(xOffset * -lightLayerScale, yOffset * -lightLayerScale, 0);
}

void World::draw(void) {

	bool editorVisible = false;
	if(editor) {
		if(editor->getVisible()) {
			editorVisible = true;
		}
	}

    int error = glGetError();
    if(error != GL_NO_ERROR) {
		out("error") << "OpenGL error (1): " << gluErrorString(error) << endl;
	}

    // Get the real screen size and 2^n version for our offscreen buffers.
    Vector2D realScreenSize = renderer->getScreenSize();
    int screenWidthPow2     = getPow2(int(realScreenSize.x));
    int screenHeightPow2    = getPow2(int(realScreenSize.y));

    // TODO: These should be set up in the editor and level data.
    bool oldSchoolLights = retroEffects; // TODO: Make this do something.
    bool oldSchoolFade   = retroEffects;

    // List of fullbright lights that will affect other layers.
    vector<Entity*> stickyLights;

    vector< vector<Entity*> > fullbrights; // Entities that ignore
                                           // lighting. Per-layer so
                                           // we can interleave them
                                           // with the actual layer
                                           // drawing.

    vector< vector<Entity*> > entsBelowZero;  // All ents with depth < 0 go into here and get sorted.
    vector< vector<Entity*> > entsAtZero;     // All ents with depth == 0 go into here and DON'T get sorted (most stuff).
    vector< vector<Entity*> > entsAboveZero;  // All ents with depth > 0 go into here and get sorted.
    vector< vector<Entity*> > lights;         // Entities flagged as being lights go here.

    vector<BoundingBox> visibleAreas; // Area of each layer that's visible to the camera.

    bool drawOnlyLights = false;

    // First pass - divide entities into buckets.
	for(int layer = 0; layer < int(layers.size()); layer++) {

        // Add a new layer to each one of our lists.
        fullbrights.push_back(vector<Entity*>());
        entsBelowZero.push_back(vector<Entity*>());
        entsAtZero.push_back(vector<Entity*>());
        entsAboveZero.push_back(vector<Entity*>());
        lights.push_back(vector<Entity*>());

        visibleAreas.push_back(getVisibleArea(layer, editorVisible));

        if(!layers[layer]->drawBuffer || !layers[layer]->lightBuffer) {
            
            // No layer buffer for this layer yet?
            if(!layers[layer]->drawBuffer)  glGenTextures(1, &layers[layer]->drawBuffer);
            if(!layers[layer]->lightBuffer) glGenTextures(1, &layers[layer]->lightBuffer);
        }

		bool drawThisLayer = true;
		
        // TODO: Remove this.
		//if(editor) {
		//	if(!editor->junkInterface.isLayerVisible(layer)) drawThisLayer = false;
        //}

        if(!layers[layer]->visible) drawThisLayer = false;

		if(drawThisLayer) {

            vector<Entity*> entsToDraw;

            // Expand the visible area a bit because things use the
            // bounding box size rather than the actual sprite area.
            visibleAreas[layer].upperLeft  = visibleAreas[layer].upperLeft  - Vector2D(256, -256);
            visibleAreas[layer].lowerRight = visibleAreas[layer].lowerRight + Vector2D(256, -256);

            layers[layer]->quadTree->getEntsTouchingBox(visibleAreas[layer], entsToDraw);
		
            // Now we have all the Entities, we have to make sure they draw
            // in the right order. Most Entities are at 0 (or should be) so
            // we don't want to waste time sorting them. The small number of
            // other ents DO need to be sorted, though.
        
            for(unsigned int i = 0; i < entsToDraw.size(); i++) {
                float depth = entsToDraw[i]->getDepth();

                if(entsToDraw[i]->getIsLight()) {

                    if(entsToDraw[i]->getFullbright()) {
                        // Sticky lights.
                        stickyLights.push_back(entsToDraw[i]);

                        //if(layer != 2 && layer != 3) {
                        //    out("warning") << "Fullbright (multi-layer) lights don't do much out of layers 2 and 3!" << endl;
                        //}

                    } else {
                        // Lights bucket.
                        lights[layer].push_back(entsToDraw[i]);
                    }

                } else {

                    if(entsToDraw[i]->getFullbright()) {
                        fullbrights[layer].push_back(entsToDraw[i]);
                    } else {

                        // Normal draw buckets.
                        if(depth < 0) {
                            entsBelowZero[layer].push_back(entsToDraw[i]);
                        } else if(depth > 0) {
                            entsAboveZero[layer].push_back(entsToDraw[i]);
                        } else {
                            entsAtZero[layer].push_back(entsToDraw[i]);
                        }
                    }
                }
            }
		
            // Maybe we can turn this off. But I'll leave it until it
            // becomes a performance issue.
            sort(lights[layer].begin(), lights[layer].end(), compareDepth);
            sort(stickyLights.begin(), stickyLights.end(), compareDepth);
            sort(fullbrights[layer].begin(), fullbrights[layer].end(), compareDepth);

            // Sort our two (hopefully relatively small) lists of ordered ents. 
            sort(entsAboveZero[layer].begin(), entsAboveZero[layer].end(), compareDepth);
            sort(entsBelowZero[layer].begin(), entsBelowZero[layer].end(), compareDepth);
        }
    }
    
    // Entities are sorted into buckets now.

    // Second pass - render layers to offscreen images.
    if(lightingEnabled) {
        
        for(int layer = 0; layer < int(layers.size()); layer++) {

            if(layers[layer]->lightingEnabled && !getLayerCombineWithPrevious(layer)) {
            
                // Set layer-specific ambient.
                glClearColor(
                    layers[layer]->ambientLight.x,
                    layers[layer]->ambientLight.y,
                    layers[layer]->ambientLight.z, 1); 
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

                // We need this so the alpha doesn't get all hosed.
                glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);

                // Reduce the drawing area of the light layer.
                pushLightLayerScale(0, 0);

                // Draw light buffer and save it.

                int drawLayer = layer;
                while(drawLayer == layer || getLayerCombineWithPrevious(drawLayer)) {

                    pushCamMatrixForLayer(drawLayer);

                    // Draw lightmap tiles.
                    if(layers[drawLayer]->tilesAreLightMap) {
                        layers[drawLayer]->tilePane.draw(visibleAreas[drawLayer], false);
                    }

                    // Draw normal lights.
                    for(unsigned int i = 0; i < lights[drawLayer].size(); i++) {
                        lights[drawLayer][i]->draw();
                    }

                    if(drawLayer == layer) {

                        // Draw fullbright lights.
                        if(layers[layer]->fullbrightLightsEnabled) {
                            for(unsigned int i = 0; i < stickyLights.size(); i++) {
                                stickyLights[i]->draw();
                            }
                        }

                    }

                    popMatrix();

                    drawLayer++;
                }

                glBindTexture(GL_TEXTURE_2D, 0);
                if(glUseProgramObjectARB) glUseProgramObjectARB(0);

                if(!drawOnlyLights) {

                    if(oldSchoolLights) {

                        // Now we need to invert everything so the subtractive
                        // blend actually works.

                        // Drawing pure white with these settings will invert
                        // all the colors.
                        glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ZERO);
                        glBlendEquation(GL_FUNC_SUBTRACT);

                        glBegin(GL_QUADS); {

                            glColor4f(1, 1, 1, 1);
                            glVertex2f(0, 0);
                            glVertex2f(realScreenSize.x, 0);
                            glVertex2f(realScreenSize.x, realScreenSize.y);
                            glVertex2f(0, realScreenSize.y);

                        } glEnd();
                    }
                }

                // And back to normal.
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                glBlendEquation(GL_FUNC_ADD);

                // Save it.
                saveAsTexture(layers[layer]->lightBuffer, screenWidthPow2 * lightLayerScale, screenHeightPow2 * lightLayerScale);

                glPopMatrix();

                // Actual drawing happens after this point...
                glClearColor(0, 0, 0, 0);
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

                drawLayer = layer;
                while(drawLayer == layer || getLayerCombineWithPrevious(drawLayer)) {

                    pushCamMatrixForLayer(drawLayer);

                    // Draw tiles.
                    bool activeLayer = false;

                    if(editor) activeLayer = editor->junkInterface.getActiveLayer() == drawLayer;
			
                    if(!layers[drawLayer]->tilesAreLightMap) {
                        layers[drawLayer]->tilePane.draw(visibleAreas[drawLayer], activeLayer);
                    }
			
                    // Draw Entities
                    for(unsigned int i = 0; i < entsBelowZero[drawLayer].size(); i++) {
                        entsBelowZero[drawLayer][i]->draw();
                    }
                    for(unsigned int i = 0; i < entsAtZero[drawLayer].size(); i++) {
                        entsAtZero[drawLayer][i]->draw();
                    }
                    for(unsigned int i = 0; i < entsAboveZero[drawLayer].size(); i++) {
                        entsAboveZero[drawLayer][i]->draw();
                    }

                    popMatrix();

                    drawLayer++;

                }

                // Draw lights on top of this.

                // This will do a subtractive blend, but only work well if
                // the light texture has been INVERTED!
                if(!drawOnlyLights) {
                    if(oldSchoolLights) {
                        glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_ONE);
                        glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
                    } else {
                        glBlendFuncSeparate(
                            GL_DST_COLOR, GL_ZERO,
                            GL_ZERO, GL_ONE);
                        glBlendEquation(GL_FUNC_ADD);
                    }
                }

                if(glUseProgramObjectARB) glUseProgramObjectARB(0);
                glBindTexture(GL_TEXTURE_2D, layers[layer]->lightBuffer);

                float u2 = realScreenSize.x / float(screenWidthPow2);
                float v2 = realScreenSize.y / float(screenHeightPow2);

                glBegin(GL_QUADS); {

                    glColor4f(1, 1, 1, 1);

                    glTexCoord2f(0, 0);
                    glVertex2f(0, 0);

                    glTexCoord2f(u2, 0);
                    glVertex2f(realScreenSize.x, 0);

                    glTexCoord2f(u2, v2);
                    glVertex2f(realScreenSize.x, realScreenSize.y);

                    glTexCoord2f(0, v2);
                    glVertex2f(0, realScreenSize.y);

                } glEnd();
            }

            // Back to normal blending.
            glBlendEquation(GL_FUNC_ADD);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

            // Draw fullbrights on top of the lit layer.
            pushCamMatrixForLayer(layer);
            for(unsigned int i = 0; i < fullbrights[layer].size(); i++) {
                fullbrights[layer][i]->draw();
            }
            popMatrix();

            // Save buffer for this layer.
            saveAsTexture(layers[layer]->drawBuffer, screenWidthPow2, screenHeightPow2);
        }
    }
		
    error = glGetError();
    if(error != GL_NO_ERROR) {
		out("error") << "OpenGL error (2): " << gluErrorString(error) << endl;
	}

    if(lightingEnabled) {
        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    // Third pass - draw all the saved layers OR draw the ents and
    // tiles now if they weren't saved earlier.
    for(int layer = 0; layer < int(layers.size()); layer++) {

        if(lightingEnabled && layers[layer]->lightingEnabled) {
        
            // Lighting enabled - just draw the saved texture.

            bool drawThisLayer = true;
		
            if(editor) {
                if(!editor->junkInterface.isLayerVisible(layer)) drawThisLayer = false;
            }

            drawThisLayer &= layers[layer]->lightingEnabled;

            if(drawThisLayer) {

                glColor4f(1, 1, 1, 1);

                float u2 = realScreenSize.x / float(screenWidthPow2);
                float v2 = realScreenSize.y / float(screenHeightPow2);

                glBindTexture(GL_TEXTURE_2D, layers[layer]->drawBuffer);

                glBegin(GL_QUADS); {

                    glTexCoord2f(0, 0);   glVertex2f(0, 0);
                    glTexCoord2f(u2, 0);  glVertex2f(realScreenSize.x, 0);
                    glTexCoord2f(u2, v2); glVertex2f(realScreenSize.x, realScreenSize.y);
                    glTexCoord2f(0, v2);  glVertex2f(0, realScreenSize.y);

                } glEnd();
            }

        } else {

            if(layers[layer]->visible) {

                // Lighting disabled - do main drawing here.

                pushCamMatrixForLayer(layer);

                // Draw tiles.
                bool activeLayer = false;

                if(editor) activeLayer = editor->junkInterface.getActiveLayer() == layer;
			
                if(!layers[layer]->tilesAreLightMap) {
                    layers[layer]->tilePane.draw(visibleAreas[layer], activeLayer);
                }
			
                // Draw Entities
                for(unsigned int i = 0; i < entsBelowZero[layer].size(); i++) {
                    entsBelowZero[layer][i]->draw();
                }
                for(unsigned int i = 0; i < entsAtZero[layer].size(); i++) {
                    entsAtZero[layer][i]->draw();
                }
                for(unsigned int i = 0; i < entsAboveZero[layer].size(); i++) {
                    entsAboveZero[layer][i]->draw();
                }

                // Draw fullbrights on top..
                for(unsigned int i = 0; i < fullbrights[layer].size(); i++) {
                    fullbrights[layer][i]->draw();
                }

                popMatrix();

            }

        }
    }

    error = glGetError();
    if(error != GL_NO_ERROR) {
		out("error") << "OpenGL error (3): " << gluErrorString(error) << endl;
	}

    // Done drawing entities and tiles.

	pushCamMatrix();
	
	// One more pass for screwed up entities on bad layers.
	/*
	BoundingBox visibleArea = getVisibleArea(3, editor->getVisible());
	vector<Entity*> entsToDraw;
	quadTree->getEntsTouchingBox(visibleArea, entsToDraw);
	for(unsigned int i = 0; i < entsToDraw.size(); i++) {
		int layer = entsToDraw[i]->getLayer();
		if(layer < 0 || layer >= int(layers.size())) entities[i]->draw();
	}
	*/
	
    // Draw visible area to show what's actually going to be on the screen...
	if(zoomLevel != 1) {
		renderer->unbindTexture();
		renderer->setColor(1, 0, 0, 0.5);
		getVisibleArea().draw(renderer);
	}

	popMatrix();
	
    if(!editorVisible) {

        // Emulate an old VGA fade by adding or subtracting instead of
        // alpha blending to black or white.
        renderer->unbindTexture();

        // Subtractive - for anything that needs to be darker.
        if(oldSchoolFade) {

            // Old-school style fade.
            glBlendFunc(GL_ONE, GL_ONE);
            glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
            glColor4f(
                1.0-subtractiveFade[0],
                1.0-subtractiveFade[1],
                1.0-subtractiveFade[2], 1);

        } else {

            // Normal alpha blend fade. Only goes to black.
            glColor4f(0, 0, 0,
                      1.0 - (subtractiveFade[0] +
                             subtractiveFade[1] +
                             subtractiveFade[2]) / 3.0 );
        } 

        glBegin(GL_QUADS); {
            glVertex2f(0, 0);
            glVertex2f(screenSize.x, 0);
            glVertex2f(screenSize.x, screenSize.y);
            glVertex2f(0, screenSize.y);
        } glEnd();

        // Additive - for fades to white, or a specific color.
        if(oldSchoolFade) {

            // Old-school fade.
            glBlendFunc(GL_ONE, GL_ONE);
            glBlendEquation(GL_FUNC_ADD);
            glColor4f(
                additiveFade[0],
                additiveFade[1],
                additiveFade[2], 1);

        } else {

            // Normal alpha blend fade. Only goes to white.
            glColor4f(1, 1, 1,
                      (additiveFade[0] +
                       additiveFade[1] +
                       additiveFade[2]) / 3.0);
        }

        glBegin(GL_QUADS); {
            glVertex2f(0, 0);
            glVertex2f(screenSize.x, 0);
            glVertex2f(screenSize.x, screenSize.y);
            glVertex2f(0, screenSize.y);
        } glEnd();

        glBlendEquation(GL_FUNC_ADD);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    }

    error = glGetError();
    if(error != GL_NO_ERROR) {
		out("error") << "OpenGL error (4): " << gluErrorString(error) << endl;
	}
}

void World::minimumUpdate(void) {
}

void World::update(void) {
	activeWorld = this;
	frameCounter++;

	addNewEntities();
	
	if(joystick) SDL_JoystickUpdate();
	
	collisionsThisFrame.clear();
	
    // Update stored mouse state.
    int mouseX;
    int mouseY;
    Uint8 buttons = SDL_GetMouseState(&mouseX, &mouseY);
    for(int i = 0; i < 5; i++) {
        mouseButtonStates[i] = (buttons & (1 << i));
    }
    lastMousePos = mouseToWorld(mouseX, mouseY);


	lua_getfield(L, LUA_GLOBALSINDEX, "worldUpdate");
	if(!lua_isnil(L, -1)) {
		if(lua_pcall(L, 0, 0, 0)) {
			out("error") << "Lua exploded because (world update): " << lua_tostring(L, -1) << endl;
		}
	} else {
		lua_pop(L, 1);
	}
	
	// First, compile a LIST of the forced updates. (Otherwise we'll
	// screw up the iterator during an update or something.)
	vector<Entity*> forceUpdatesThisFrame;
	
	// First, let's do all the forced updates.
	for(map<Entity*, bool>::iterator i = forceUpdateList.begin(); i != forceUpdateList.end(); i++) {
		assert(i->second == true);
		forceUpdatesThisFrame.push_back(i->first);
	}
	
	for(unsigned int i = 0; i < forceUpdatesThisFrame.size(); i++) {
		if(checkExclusive(forceUpdatesThisFrame[i])) {
			forceUpdatesThisFrame[i]->update();
		}
	}
	
	for(int i = 0; i < int(layers.size()); i++) {

		// Get the visible area, plus some space in each direction.
		BoundingBox visibleArea = getVisibleArea(i).expand(Vector2D(1024, 1024)).expand(Vector2D(-1024, -1024));
		vector<Entity*> entsToUpdate;
		
		// Get all the visible Entities.
        layers[i]->quadTree->getEntsTouchingBox(visibleArea, entsToUpdate);
		
		// Call update() on them all (physics and main stuff).
		for(unsigned int j = 0; j < entsToUpdate.size(); j++) {
			if(checkExclusive(entsToUpdate[j])) {
				if(!entsToUpdate[j]->getForceUpdate()) {
					entsToUpdate[j]->update();
				}
			}
		}
		
		// Call postUpdate() on them all (physics response).
		for(unsigned int j = 0; j < entsToUpdate.size(); j++) {
			if(checkExclusive(entsToUpdate[j])) {
				if(!entsToUpdate[j]->getForceUpdate()) {
					entsToUpdate[j]->postUpdate();
				}
			}
		}
	}
	
	// Do all forced postUpdates.
	for(unsigned int i = 0; i < forceUpdatesThisFrame.size(); i++) {
		forceUpdatesThisFrame[i]->postUpdate();
	}

	
	lua_getfield(L, LUA_GLOBALSINDEX, "worldPostUpdate");
	if(!lua_isnil(L, -1)) {
		if(lua_pcall(L, 0, 0, 0)) {
			out("error") << "Lua exploded because (world postupdate): " << lua_tostring(L, -1) << endl;
		}
	} else {
		lua_pop(L, 1);
	}

	cleanUpDeadEntities();
	
	textureManager->cleanOldTextures();

	// Clean up the exclusive updates list.
	exclusiveUpdateList.clear();
	
	if(lua_gettop(L) != 0) {
		out("error") << "Lua stack corruption." << endl;
		lua_pop(L, lua_gettop(L));
	}
	
}

void World::addNewEntities(void) {
	// Add the new entities into the list.
	for(unsigned int i = 0; i < newEntities.size(); i++) {
		if(!newEntities[i]->dead) {
		
			int layer = newEntities[i]->getLayer();

            // FIXME: This should probably still be a fatal error.
			//assert(layer >= 0 && layer < int(layers.size()));

            if(layer < 0 || layer >= getNumLayers()) {

                out("error") << "Entity " << newEntities[i]->getName() << " is on an invalid layer. Killing it." << endl;
                delete newEntities[i];

            } else {

                entities.push_back(newEntities[i]);

                if(!newEntities[i]->quadTreeNode) {
                    layers[layer]->quadTree->insert(newEntities[i]);
                }
            }

		} else {
			
			// It's possible that things will be cleared before they're
			// added...
			delete newEntities[i];
			
		}
	}
	newEntities.clear();
}

void World::cleanUpDeadEntities(void) {
	// Clear out all the dead entities.
	for(unsigned int i = 0; i < entities.size(); i++) {
		if(entities[i]->dead) {
			Entity *oldEnt = entities[i];
			entities[i] = entities[entities.size() - 1];
			entities.erase(entities.end() - 1);
			delete oldEnt;
			i--;
		}
	}
}

Vector2D World::mouseToWorld(int x, int y, float camScale) const {
	Vector2D mouseOffset(
		(cameraLoc.x * camScale) - screenSize.x / (2.0 * zoomLevel),
		(cameraLoc.y * camScale) - screenSize.y / (2.0 * zoomLevel));

	Vector2D mouseLoc(x / zoomLevel, (screenSize.y - y) / zoomLevel);
	
	return mouseOffset + mouseLoc;
}

Vector2D World::mouseEventToWorld(SDL_Event *event, float camScale) const {
	switch(event->type) {
		case SDL_MOUSEBUTTONDOWN:
		case SDL_MOUSEBUTTONUP:
			return mouseToWorld(event->button.x, event->button.y, camScale);
		case SDL_MOUSEMOTION:
			return mouseToWorld(event->motion.x, event->motion.y, camScale);
		default:
			return Vector2D(0, 0);
	}
}

Vector2D World::mouseRelToWorld(SDL_Event *event) const {
	if(event->type == SDL_MOUSEMOTION) {
		return Vector2D(event->motion.xrel, -event->motion.yrel) / zoomLevel;
	}
	return Vector2D(0, 0);
}

void  World::pushCamMatrix(float cameraLocScale) const {
	renderer->pushMatrix();

#ifdef CAMERA_OFFSET_WHOLE_PIXELS

    // Prevent getting off by one pixel on some GPUs.
    float zoomDrawHack = zoomLevel;
    if(zoomLevel == 0.25) zoomDrawHack = 0.251;

	Vector2D translation(
		int(-(cameraLoc.x * cameraLocScale) + screenSize.x / (2.0 * zoomDrawHack)),
		int(-(cameraLoc.y * cameraLocScale) + screenSize.y / (2.0 * zoomDrawHack)));

    // Make really sure we're at integer increments across pixels
    // here.
    translation = translation * zoomLevel;
    translation.x = int(translation.x);
    translation.y = int(translation.y);
    translation = translation / zoomLevel;

#else

	Vector2D translation(
		(-(cameraLoc.x * cameraLocScale) + screenSize.x / (2.0 * zoomLevel)),
		(-(cameraLoc.y * cameraLocScale) + screenSize.y / (2.0 * zoomLevel)));

#endif
	
	renderer->scale(zoomLevel, zoomLevel, 1);
	renderer->translate(translation.x, translation.y, 0);
}

float World::getCamScaleForLayer(int layer) const {

    if(layer < 0 || layer >= int(layers.size())) {
        return 1;
    }

    return layers[layer]->cameraPosScale;

}

void World::setCamScaleForLayer(int layer, float cameraPosScale) {

    if(layer < 0 || layer >= int(layers.size())) {
        out("error") << "Trying to set camera pos scale for bad layer: " << layer << endl;
        return;
    }

    layers[layer]->cameraPosScale = cameraPosScale;
}

void World::pushCamMatrixForLayer(int layer) const {
	pushCamMatrix(getCamScaleForLayer(layer));
}

void  World::popMatrix(void) const {
	renderer->popMatrix();
}


QuadTree *World::getQuadTree(int layer) {
	assert(layer >= 0 && layer < int(layers.size()));
	return layers[layer]->quadTree;
}

lua_State *World::getLuaState(void) {
	return L;
}

TextureManager *World::getTextureManager(void) const {
	return textureManager;
}

FontManager *World::getFontManager(void) const {
	return fontManager;
}

void World::saveEntities(const std::string &fileName, const std::vector<std::string> &exceptionList) {

	if(!fileName.size()) {
		out("error") << "Save attempted with no file name." << endl;;
	}

	string realFileName = getRealFile(string("levels/") + fileName, true);
	if(!realFileName.size()) {
		out("error") << "Save attempted with no save slot selected." << endl;
		return;
	}
	
	FileSystem::makePathForSave(realFileName);
	
	// Make a new document.
	TiXmlDocument *doc = new TiXmlDocument();
	
	// Serialize everything in the world and link it.
	TiXmlElement *e = toXml(exceptionList);
	doc->LinkEndChild(e);

	// Make a printer and give it the data.
	TiXmlPrinter printer;
	printer.SetIndent("\t");
	doc->Accept(&printer);
	delete doc;
	
	// Write it all out.
	// TODO: Check for opern/writing error?
	ofstream outFile;
	outFile.open(realFileName.c_str());
	outFile << printer.CStr();
	outFile.close();

}

TiXmlElement *World::toXml(const std::vector<std::string> &exceptionList) {
	
	// FIXME: This function needs reworking. Seriously, virtual
	//   functions exist for a reason (blargh).
	
	TiXmlElement *e = new TiXmlElement("World");
	for(unsigned int i = 0; i < entities.size() + newEntities.size(); i++) {
		Entity *ent;
		
		if(i >= entities.size()) {
			ent = newEntities[i - entities.size()];
		} else {
			ent = entities[i];
		}
		
		bool doSave = true;
		string entName = ent->getName();
		for(unsigned int j = 0; j < exceptionList.size(); j++) {
			if(exceptionList[j] == entName) {
				doSave = false;
				break;
			}
		}
		
		if(doSave) {
			if(!ent->getDead()) {
				
				// TODO: Virtual functions exist for a reason. Take out
				//   Entity subclass-specific handling.
				
				if(dynamic_cast<TerrainBlock*>(ent)) {
					// TerrainBlocks are our built-in, so we save them.
					e->LinkEndChild(ent->toXml());
					
				} else if(ScriptEntity *scriptEnt = dynamic_cast<ScriptEntity*>(ent)) {
					if(scriptEnt->isSavable()) {
						TiXmlElement *newEl = scriptEnt->toXml();
						e->LinkEndChild(newEl);
					}
				} else if(dynamic_cast<SceneryChunk*>(ent)) {
					// Save SceneryChunks...
					e->LinkEndChild(ent->toXml());
				} else {
					// Hmmmmmmmmmmmmm...
				}
			}
		}		
	}

	return e;
}

void World::addEntitiesFromXml(TiXmlElement *worldElement) {

	// Iterate through all the XML elements.
	TiXmlElement *e = worldElement->FirstChildElement();
	while(e) {
		
		// TerrainBlocks are built-in, so we handle them here.
		if(string(e->Value()) == string("TerrainBlock")) {
			
			// Create the new TerrainBlock.
			TerrainBlock *tb = new TerrainBlock(this, e);
			tb->buildBoundingBox();
		}
		
		// TODO: Remove this legacy stuff for dealing with old crap.
		// Other spawners.
		if(string(e->Value()) == string("Spawner")) {
			// Get the spawner base class onto the stack.
			lua_getfield(L, LUA_GLOBALSINDEX, "Spawner");
			
			createScriptEntityWithXml(this, e);
			
			// Pop the spawner base class and the entity.
			lua_pop(L, 2);
		}
		
		// ScriptEntities
		if(string(e->Value()) == string("ScriptEntity")) {

			string metatableName = "";
			if(e->Attribute("Metatable")) {
				metatableName = e->Attribute("Metatable");
				
				// Get the base class onto the stack.
				lua_getfield(L, LUA_GLOBALSINDEX, metatableName.c_str());
				
				if(lua_isnil(L, -1)) {
					
					out("error") << "XML error: ScriptEntity metatable doesn't match any known type." << endl;
					lua_pop(L, 1);
					
				} else {
					
					createScriptEntityWithXml(this, e);
					
					// Pop the spawner base class and the entity.
					lua_pop(L, 2);
				}
			} else {
				out("error") << "XML error: ScriptEntity didn't have a metatable." << endl;
			}
			
		}
		
		// SceneryChunks
		if(string(e->Value()) == string("SceneryChunk")) {
			new SceneryChunk(this, e);
		}
		
		assert(lua_gettop(L) == 0);
		
		// Move to the next element.
		e = e->NextSiblingElement();
	}
}
	

std::vector<std::string> World::getSpawnList(void) {
	vector<string> spawnList;
	
	for(map<string, spawnMapTypes>::iterator i = spawnMap.begin(); i != spawnMap.end(); i++) {
		spawnList.push_back(i->first);
	}
	
	return spawnList;
}

void World::addSpawn(const std::string &spawnName) {
	spawnMap[spawnName] = SPAWNTYPE_SPAWNER;
}

void World::addScenery(const std::string &sceneryName, const std::string &textureName, const BoundingBox &area) {
	spawnMap[sceneryName] = SPAWNTYPE_SCENERY;
	SceneryDefinition def;
	def.textureName = textureName;
	def.area = area;
	sceneryDefs[sceneryName] = def;
}

EditorUI *World::getEditor(void) const {
	return editor;
}


BoundingBox World::getVisibleArea(int layer, bool includeZoom) const {
	float scale = zoomLevel;
	float cameraScale = getCamScaleForLayer(layer);
	if(!includeZoom) scale = 1;
	return BoundingBox(
		(cameraLoc * cameraScale) - (screenSize / 2) / scale - Vector2D(0.1, 0.1),
		(cameraLoc * cameraScale) + (screenSize / 2) / scale + Vector2D(0.1, 0.1));
}


int World::getControlAxisState(int axis) {
	// TODO: Make keyboard axes configurable.
	
	// Keyboard stuff before even checking joystick
	unsigned char *keys = SDL_GetKeyState(NULL);
	
	// Axis 0 (left/right)
	if(axis == 0) {
		if(keys[SDLK_LEFT] && !keys[SDLK_RIGHT]) {
			return -32768;
		}
		if(!keys[SDLK_LEFT] && keys[SDLK_RIGHT]) {
			return 32767;
		}
	}
	
	// Axis 1 (up/down)
	if(axis == 1) {
		if(keys[SDLK_UP] && !keys[SDLK_DOWN]) {
			return -32768;
		}
		if(!keys[SDLK_UP] && keys[SDLK_DOWN]) {
			return 32767;
		}
	}
	
	// Joystick stuff.
	if(joystick) {
		// TODO: Deadzone?
		return SDL_JoystickGetAxis(joystick, axis);
	}
	
	// Not one of the two directional axes, but no joystick. vOv
	return 0;
}

bool World::getControlButtonState(int button) {
	// TODO: Make keyboard buttons configurable.
	
	// Keyboard stuff before even checking joystick
	unsigned char *keys = SDL_GetKeyState(NULL);
	
	if(button == 0) {
		if(keys[SDLK_LCTRL] || keys[SDLK_x] || keys[SDLK_RETURN]) {
			return true;
		}
	}
	
	if(button == 1) {
		if(keys[SDLK_LALT] || keys[SDLK_z]) {
			return true;
		}
	}
	
	if(button == 2) {
		if(keys[SDLK_LSHIFT] || keys[SDLK_c]) {
			return true;
		}
	}
	
	if(button == 9) {
		if(keys[SDLK_TAB] || keys[SDLK_v]) {
			return true;
		}
	}
	
	if(joystick) {
		return SDL_JoystickGetButton(joystick, button);
	}
	
	return false;
}

void World::setForceUpdate(Entity *ent, bool forceUpdate) {
	if(forceUpdate) {
		forceUpdateList[ent] = true;
	} else {
		forceUpdateList.erase(ent);
	}
}

void World::getTileEntities(const BoundingBox &bb, int layer, std::vector<Entity*> &tileEnts) const {

	if(layer >= int(layers.size()) || layer < 0) return;

	int tileSize = layers[layer]->tilePane.getTileSize();

	int lx = int(bb.upperLeft.x)  / tileSize - 1;
	int ly = int(bb.lowerRight.y) / tileSize - 1;
	int ux = int(bb.lowerRight.x) / tileSize + 1;
	int uy = int(bb.upperLeft.y)  / tileSize + 1;

	if(lx < 0) lx--;
	if(ly < 0) ly--;
	if(ux < 0) ux--;
	if(uy < 0) uy--;
	
	for(int x = lx; x <= ux; x++) {
		for(int y = uy; y >= ly; y--) {
			if(layers[layer]->tilePane.getTile(x, y) != NULLTILE) {
                TileEntity *tileEnt = new TileEntity(x, y, tileSize);
                tileEnt->setLayer(layer, false);
				tileEnts.push_back(tileEnt);
			}
		}
	}
}

void World::setDefaultAmbient(void) {
    for(int i = 0; i < int(layers.size()); i++) {
        layers[i]->ambientLight = Vector3D(1, 1, 1);
    }
}

void World::saveTiles(const std::string &fileName) {

    //string saveName = getRealFile(string("levels/") + FileSystem::getBaseName(fileName + ".tiles.rle2"));
    string saveName = fileName + ".tiles.rle2";

    out("system") << "Saving tiles: " << saveName << endl;
    out("system") << fileName << endl;
    
    SimpleBuffer outBuffer;

    // File version.
    int fileVersion = 5;
    outBuffer.addData(&fileVersion, sizeof(fileVersion));

    // Number of layers.
    int numLayers = int(layers.size());
    outBuffer.addData(&numLayers, sizeof(numLayers));

    // Save retro flag.
    outBuffer.addData(&retroEffects, sizeof(retroEffects));

    for(int i = 0; i < numLayers; i++) {

        // Save layer name.
        int layerNameSize = layers[i]->name.size();
        string layerName = layers[i]->name;
        outBuffer.addData(&layerNameSize, sizeof(int));
        outBuffer.addData(layerName.c_str(), layerNameSize);

        // Save camera scale.
        outBuffer.addData(&layers[i]->cameraPosScale, sizeof(float));

        // Save tiles.
        layers[i]->tilePane.saveSimpleBuf(&outBuffer);
        
        // Save ambient lights.
        Vector3D ambientColor = layers[i]->ambientLight;
        float out;
        out = ambientColor.x; outBuffer.addData(&out, sizeof(out));
        out = ambientColor.y; outBuffer.addData(&out, sizeof(out));
        out = ambientColor.z; outBuffer.addData(&out, sizeof(out));

        // Save lighting flag.
        outBuffer.addData(&layers[i]->lightingEnabled, sizeof(bool));

        // Save fullbright lighting flag.
        outBuffer.addData(&layers[i]->fullbrightLightsEnabled, sizeof(bool));

        // Save combined flag.
        outBuffer.addData(&layers[i]->combineWithPreviousLayer, sizeof(bool));

        // Save lightmap flag.
        outBuffer.addData(&layers[i]->tilesAreLightMap, sizeof(bool));

    }

    FileSystem::saveFile(saveName, outBuffer.getData(), outBuffer.getLength());

}

void World::loadTiles(const std::string &fileName) {

	out("world") << "Loading tiles from " << fileName << endl;
	
    string fixedName = fileName;

	if(strEndsWith(".tiles.rle", fixedName)) {
        out("error") << "Stop adding .rle to the end of tile filenames. Let the engine do that." << endl;
        fixedName = fixedName.substr(0, fixedName.size() - 10);
    }

	// Load TilePanes the new way.
	string tilePaneFile = activeWorld->getRealFile(string("levels/") + FileSystem::getBaseName(fixedName + ".tiles.rle"));
	
	bool showConversionWarning = false;
	
	if(strEndsWith(".gz", tilePaneFile)) {
		tilePaneFile = tilePaneFile.substr(0, tilePaneFile.size() - 2) + "rle";
		showConversionWarning = true;
	}
	
    int rle2Length;
    char *rle2Contents;
    rle2Contents = FileSystem::loadFile(tilePaneFile + "2", &rle2Length);

    if(rle2Contents) {

        // Load map the current way.

        out("system") << "Loading tiles from " << tilePaneFile << "2" << endl;

        SimpleBuffer buf(rle2Contents, rle2Length);
        delete[] rle2Contents;
	
        // Going to use this for colors in case we change Vector3D
        // some time later to use the other data type.
        struct {
                float r, g, b;
        } tmpCol;

        setDefaultAmbient();

        // Load version.
        int versionNum = 0;
        buf.getDataAndAdvance(&versionNum, sizeof(versionNum));

        // Load number of layers.
        int numLayers = 0;
        buf.getDataAndAdvance(&numLayers, sizeof(numLayers));

        // Load retro effects flag.
        if(buf.getDataAndAdvance(NULL, 0)) {
            buf.getDataAndAdvance(&retroEffects, sizeof(retroEffects));
        } else {
            retroEffects = true;
        }
        
        // Load all individual layers. For the most part we overwrite
        // whatever is already loaded, remove the layers that are
        // extra, and add layers that aren't there. (Sometimes we might
        // want to load tiles without changing entities.)

        int i;
        for(i = 0; i < numLayers; i++) {

            if(i >= getNumLayers()) {
                createLayer();
            }

            if(versionNum > 1) {

                // Get the layer name.
                int layerNameSize = 0;
                char *layerNameBuf;
                buf.getDataAndAdvance(&layerNameSize, sizeof(layerNameSize));
                layerNameBuf = new char[layerNameSize + 1];
                memset(layerNameBuf, 0, layerNameSize + 1);
                buf.getDataAndAdvance(layerNameBuf, layerNameSize);
                layerNameBuf[layerNameSize] = 0;

                layers[i]->name = layerNameBuf;
                delete[] layerNameBuf;

                // Get camera scale.
                buf.getDataAndAdvance(&layers[i]->cameraPosScale, sizeof(float));

            } else {

                // Settle for a default name and scale.
                ostringstream str;
                str << "Layer " << i;
                layers[i]->name = str.str();

                layers[i]->cameraPosScale = 1;

            }

            // Load tiles.
            if(buf.getDataAndAdvance(NULL, 0)) {
                layers[i]->tilePane.loadSimpleBuf(&buf);
            } else {
                layers[i]->tilePane.clear();
                layers[i]->tilePane.setTextureNum(-1);
            }

            // Load ambient values for each layer.
            if(buf.getDataAndAdvance(&tmpCol, sizeof(tmpCol))) {
                layers[i]->ambientLight = Vector3D(tmpCol.r, tmpCol.g, tmpCol.b);
            }

            // Lighting info.
            if(versionNum > 2) {
                
                // Get lighting flag.
                buf.getDataAndAdvance(&layers[i]->lightingEnabled, sizeof(bool));

                // Get fullbrights flag.
                buf.getDataAndAdvance(&layers[i]->fullbrightLightsEnabled, sizeof(bool));

            } else {

                // Set up reasonable defaults.

                if(i >= 2 && i <= 4) {
                    layers[i]->fullbrightLightsEnabled = true;
                } else {
                    layers[i]->fullbrightLightsEnabled = false;
                }

                layers[i]->lightingEnabled = true;

            }

            if(versionNum > 3) {

                // Get combined flag.
                buf.getDataAndAdvance(&layers[i]->combineWithPreviousLayer, sizeof(bool));

            } else {

                layers[i]->combineWithPreviousLayer = false;

            }

            if(versionNum > 4) {

                // Get lightmap flag.
                buf.getDataAndAdvance(&layers[i]->tilesAreLightMap, sizeof(bool));

            } else {

                layers[i]->tilesAreLightMap = false;

            }

        }

        // Delete any extra layers.
        while(i < getNumLayers()) {
            deleteLayer(getNumLayers() - 1);
        }

    } else {

        // Fall back to old RLE.

        out("warning") << "Loading tiles from deprecated " << tilePaneFile << endl;

        int tpFileLength;
        char *tpFileContents;
        tpFileContents = FileSystem::loadFile(tilePaneFile, &tpFileLength);
	
        if(tpFileContents) {
	
            SimpleBuffer buf(tpFileContents, tpFileLength);
            delete[] tpFileContents;
	
            for(int i = 0; i < int(layers.size()); i++) {
                if(buf.getDataAndAdvance(NULL, 0)) {
                    layers[i]->tilePane.loadSimpleBuf(&buf);
                } else {
                    layers[i]->tilePane.clear();
                    layers[i]->tilePane.setTextureNum(-1);
                }

                if(i >= 2 && i <= 4) {
                    layers[i]->fullbrightLightsEnabled = true;
                } else {
                    layers[i]->fullbrightLightsEnabled = false;
                }

                layers[i]->lightingEnabled = true;

            }
		
            // Load ambient values for each layer.
            if(buf.getDataAndAdvance(NULL, 0)) {
            
                // Going to use this in case we change Vector3D some time
                // later to use the other data type.
                struct {
                        float r, g, b;
                } tmpCol;

                for(int i = 0; i < int(layers.size()); i++) {
                    if(buf.getDataAndAdvance(&tmpCol, sizeof(tmpCol))) {
                        layers[i]->ambientLight = Vector3D(tmpCol.r, tmpCol.g, tmpCol.b);
                    }
                }

            } else {
            
                setDefaultAmbient();

            }

            // Load retro effects flag.
            if(buf.getDataAndAdvance(NULL, 0)) {
                buf.getDataAndAdvance(&retroEffects, sizeof(retroEffects));
            } else {
                retroEffects = true;
            }

            if(showConversionWarning) {
                out("warning") << "Converted a gzipped tiles file name to RLE. Fix this in data: " << fileName << endl;
            }
	
        } else {

            // FIXME: This will probably crash if the number of
            // already existing layers is less than maxLayers.
	
            // TODO: Make this go through FileSystem for archives.
            // TODO: Actually, just remove this, because the RLE compressed
            //   stuff WILL go through archives.
		
            // Load TilePanes, if the file exists for them.
            gzFile in = gzopen(activeWorld->getRealFile(string("levels/") + fileName).c_str(), "rb");

            if(!in) {
                out("error") << "Couldn't load tiles file " << fileName << "." << endl;
            }
		
            for(int i = 0; i < int(layers.size()); i++) {
                if(in) {
                    layers[i]->tilePane.load(in);
                } else {
                    layers[i]->tilePane.clear();
                    layers[i]->tilePane.setTextureNum(-1);
                }
            }
		
            if(in) {
                out("warning") << "Loaded an old gzipped tiles file. Make sure you load and re-save this: " << tilePaneFile << endl;
                gzclose(in);
            }
        }
    }

    if(editor) {
        editor->junkInterface.updateLayerList(this);
    }
}

void World::loadEntities(const std::string &fileName, bool forceAbsolutePath) {
	out("world") << "Loading Entities from " << fileName << endl;
	
	string realFileName = fileName;
	if(!forceAbsolutePath) {
		realFileName = activeWorld->getRealFile(string("levels/") + fileName);
	}
	
	// TODO: Find a better way to tell the game what level we're on.
	//   This will break if we ever try to add Entities in from a script
	//   that isn't part of a normal level transition!
//#	ifdef WIN32
//		string justTheName = FileSystem::stripDataPath(realFileName, FileSystem::getParentName(realFileName) + "\\");
//#	else
		string justTheName = FileSystem::stripDataPath(realFileName, FileSystem::getParentName(realFileName) + "/");
//#	endif
	lua_pushstring(L, justTheName.c_str());
	lua_setglobal(L, "lastLoadedFile");
	lastLoadedMapName = justTheName;
	
	if(editor) {
		editor->junkInterface.levelFileName = lastLoadedMapName;
	}
	
	// Load the document.
	TiXmlDocument *doc = loadXml(realFileName.c_str());
	if(doc) {
	
		// Add all the Entites from the XML.
		TiXmlElement *worldElement = doc->FirstChildElement("World");
		addEntitiesFromXml(worldElement);
		
		// Lacking dynamic resizing of the quadtree, let's just do it
		// upon loading the level.
		for(int i = 0; i < int(layers.size()); i++) {
			getQuadTree(i)->resizeToFitAll();
		}
		
	} else {
		out("error") << "Couldn't load XML file " << realFileName << "." << endl;
	}
	delete doc;
}

void World::clearAllTiles(void) {
	for(int i = 0; i < int(layers.size()); i++) {
        layers[i]->tilePane.clear();
	}	
}

int World::getTile(int x, int y, int layer) const {
	assert(layer >= 0 && layer < int(layers.size()));
    return layers[layer]->tilePane.getTile(x, y);
}

void World::setTile(int x, int y, int layer, int tile) {
	assert(layer >= 0 && layer < int(layers.size()));
	layers[layer]->tilePane.setTile(x, y, tile);
}

int World::getTileSize(int layer) {
	assert(layer >= 0 && layer < int(layers.size()));
	return layers[layer]->tilePane.getTileSize();
}

void World::setGameName(const std::string &gameName) {
	this->gameName = gameName;

    if(editor) {
        editor->junkInterface.gameName = gameName;
    }
}

std::string World::getGameName(void) const {
	return gameName;
}

void World::setSaveName(const std::string &saveName) {
	this->saveName = saveName;
}

std::string World::getSaveName(void) const {
	return saveName;
}

std::string World::getRealFile(const std::string &inFileName, bool saveOnly) const {

    string fileName;
    vector<string> multiNames;
    stringTokenize(inFileName, ":", multiNames, true);

    if(multiNames.size() > 1) {

        // Multiple file name. Needs to be split and tried again, then
        // put back together.

        ostringstream realRet;
        string dirName = FileSystem::getParentName(multiNames[0]) + "/";
        multiNames[0] = FileSystem::getBaseName(multiNames[0]);

        bool first = true;

        for(unsigned int i = 0; i < multiNames.size(); i++) {

            if(first) {
                first = false;
            } else {
                realRet << ":";
            }

            realRet << getRealFile(dirName + multiNames[i], saveOnly);
        }

        return realRet.str();
    }

    fileName = multiNames[0];
	
	if(saveOnly) {
		if(!saveName.size()) return "";
		
		return string("saves/") + gameName + "/" + saveName + string("/") + fileName;
	}
	
    string ret = "";

	if(saveName.size()) {
		// First check the current save directory.
		string saveFile = string("saves/") + gameName + "/" + saveName + string("/") + fileName;
		if(FileSystem::fileExists(saveFile)) {

            ret = saveFile;

		}

	}

    if(!ret.size()) {
		
        // Then the game directory
        string gameFile = string("games/") + gameName + string("/") + fileName;
        if(FileSystem::fileExists(gameFile)) {

            ret = gameFile;

        } else {
	
            // Then the common stuff
            string commonFile = string("games/common/") + fileName;
            if(FileSystem::fileExists(commonFile)) {

                ret = commonFile;

            } else {
	
                // If none of those work, check the current directory
                if(FileSystem::fileExists(fileName)) {
                    // TODO: Spit out a warning here.
                    ret = fileName;
                }
            }
        }
    }

    // Add all the multiname junk back on.

    ostringstream realRet;

    realRet << ret;

    if(ret.size()) {
        for(unsigned int i = 1; i < multiNames.size(); i++) {
            realRet << ":" << multiNames[i];
        }
    }

	return realRet.str();
}

void World::setExclusiveUpdatesThisFrame(const std::vector<std::string> &exclusiveNames) {
	for(unsigned int i = 0; i < exclusiveNames.size(); i++) {
		exclusiveUpdateList[exclusiveNames[i]] = true;
	}
}

bool World::checkExclusive(const Entity *ent) const {
	if(!exclusiveUpdateList.size()) return true;
	
	std::map<std::string, bool>::const_iterator i = exclusiveUpdateList.find(ent->getName());
	
	if(i != exclusiveUpdateList.end()) {
		return i->second;
	}
	
	return false;
}

void World::unnameDeadEntities(void) {
	// TODO: Combine these both.
	for(unsigned int i = 0; i < entities.size(); i++) {
		if(entities[i]->getDead()) {
			//cout << "Clearing name for: " << entities[i]->getName() << endl;
			entities[i]->setName(getUnusedName("deadEnt_"));
		}
	}
	for(unsigned int i = 0; i < newEntities.size(); i++) {
		if(newEntities[i]->getDead()) {
			//cout << "Clearing name for: " << newEntities[i]->getName() << endl;
			newEntities[i]->setName(getUnusedName("deadEnt_"));
		}
	}
}

Renderer *World::getRenderer(void) {
	return renderer;
}

void World::resetListColors(void) {
	for(int i = 0; i < int(layers.size()); i++) {
        layers[i]->tilePane.resetListColors();
	}
}

string World::getLastLoadedMapName(void) {
	return lastLoadedMapName;
}

std::string fixDeprecatedPath(const std::string &str) {
	
	string ret = str;
	
	if(strStartsWith("data/", ret)) {
		out("warning") << "Removed deprecated data prefix from " << str << endl;
		ret = ret.c_str() + 5;
	}
	
	if(strEndsWith(".png", ret)) {
		out("warning") << "Changing PNG extension to TGA in " << str << endl;
		ret = ret.substr(0, ret.size() - 4) + ".tga";
	}
	
	return ret;
}

void World::reloadTileLists(void) {
	
	for(int i = 0; i < int(layers.size()); i++) {
		layers[i]->tilePane.setTextureNum(layers[i]->tilePane.getTextureNum());
	}
	
}

void World::setSubtractiveFade(float r, float g, float b) {
	subtractiveFade[0] = r;
	subtractiveFade[1] = g;
	subtractiveFade[2] = b;
	fadeCountdown = 0;
}

void World::setAdditiveFade(float r, float g, float b) {
	additiveFade[0] = r;
	additiveFade[1] = g;
	additiveFade[2] = b;
	fadeCountdown = 0;
}

bool World::getMouseButtonState(int button) {
    if(button < 0 || button >= 5) return false;
    return mouseButtonStates[button];
}

Vector2D World::getMousePos(void) {
    return lastMousePos;
}

void World::setMousePos(const Vector2D &pos) {
    lastMousePos = pos;
}

void World::handleMouseEvent(SDL_Event *event) {

    Vector2D pos;
    string handlerFuncName;

    switch(event->type) {

    case SDL_MOUSEMOTION:
        pos = mouseToWorld(event->motion.x, event->motion.y);
        handlerFuncName = "handleMouseMotion";
        break;

    case SDL_MOUSEBUTTONDOWN:
        pos = mouseToWorld(event->button.x, event->button.y);
        handlerFuncName = "handleMouseDown";
        break;

    case SDL_MOUSEBUTTONUP:
        pos = mouseToWorld(event->button.x, event->button.y);
        handlerFuncName = "handleMouseUp";
        break;
    }

    lastMousePos = pos;

    // So we never recurse infinitely here (for example when moving
    // the mouse causes the camera to move, which calls this again).
    if(handlingMouseEventNow) return;
    handlingMouseEventNow = true;

	lua_getfield(L, LUA_GLOBALSINDEX, handlerFuncName.c_str());

	if(!lua_isnil(L, -1)) {

        lua_pushnumber(L, pos.x);
        lua_pushnumber(L, pos.y);
   
        if(event->type != SDL_MOUSEMOTION) {
            lua_pushnumber(L, event->button.button);
        }

		if(lua_pcall(L, (event->type == SDL_MOUSEMOTION) ? 2 : 3, 0, 0)) {
			out("error") << "Lua exploded because (mouse handling): " << lua_tostring(L, -1) << endl;
		}
	} else {
		lua_pop(L, 1);
	}

    handlingMouseEventNow = false;

}

bool World::getIsInMouseEvent(void) {
    return handlingMouseEventNow;
}

// ---------------------------------------------------------
//  Layer manipulation junk...
// ---------------------------------------------------------

int World::getNumLayers(void) {
    return layers.size();
}

std::string World::getLayerName(int layerNum) {

    if(layerNum >= int(layers.size()) || layerNum < 0) {
        return "(invalid layer)";
    }

    return layers[layerNum]->name;

}

#define checkLayerValid(x, y) {                   \
    if(layerNum < 0) { \
      out("error") << "Invalid layer specified for " << __FUNCTION__ << endl; \
      return y; \
    } \
    if(layerNum >= int(layers.size())) { \
      out("error") << "Layer doesn't exist for " << __FUNCTION__ << endl;  \
      return y; \
    } \
}
    
void World::setLayerName(int layerNum, const std::string &layerName) {
    checkLayerValid(layerNum, );
    layers[layerNum]->name = layerName;

    if(editor) {
        editor->junkInterface.updateLayerList(this);
    }
}

bool World::getLayerVisible(int layerNum) {
    checkLayerValid(layerNum, true);
    return layers[layerNum]->visible;
}

void World::setLayerVisible(int layerNum, bool visible) {
    checkLayerValid(layerNum, );
    layers[layerNum]->visible = visible;

    if(editor) {
        editor->junkInterface.updateLayerList(this);
    }
}

void World::swapLayers(int l1, int l2) {
    
    Layer *tmp;

    if(l1 < 0 || l2 < 0 || l1 > int(layers.size() - 1) || l2 > int(layers.size() - 1)) {
        return;
    }

    // Swap the layer pointers.
    tmp = layers[l1];
    layers[l1] = layers[l2];
    layers[l2] = tmp;

    layers[l1]->tilePane.layer = l1;
    layers[l2]->tilePane.layer = l2;

    // Fix up all Entity layer numbers.
    vector<Entity*> layer1Ents;
    vector<Entity*> layer2Ents;

    layers[l1]->quadTree->getAllEntities(layer1Ents);
    layers[l2]->quadTree->getAllEntities(layer2Ents);

    for(unsigned int i = 0; i < layer1Ents.size(); i++) {
        layer1Ents[i]->setLayer(l1, false);
    }

    for(unsigned int i = 0; i < layer2Ents.size(); i++) {
        layer2Ents[i]->setLayer(l2, false);
    }

    if(editor) {
        editor->junkInterface.updateLayerList(this);
    }

}

void World::forceEditorUpdateLayerList(void) {
    if(editor) {
        editor->junkInterface.updateLayerList(this);
    }
}

void World::createLayer(void) {
    
    Layer *layer = new Layer();
    layer->name = "New Layer";
    layer->tilePane.layer = getNumLayers();
    layer->tilePane.setWorld(this);

    layers.push_back(layer);

    forceEditorUpdateLayerList();
}

void World::deleteLayer(int layerNum) {
    
    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return;
    }

    layers.erase(layers.begin() + layerNum);

    fixupLayerNumbers();

    forceEditorUpdateLayerList();
}

void World::fixupLayerNumbers(void) {

    for(int i = 0; i < getNumLayers(); i++) {
        
        layers[i]->tilePane.layer = i;

        // Fix up all Entity layer numbers.
        vector<Entity*> layerEnts;

        layers[i]->quadTree->getAllEntities(layerEnts);

        for(unsigned int j = 0; j < layerEnts.size(); j++) {
            layerEnts[j]->setLayer(i, false);
        }

    }

}

int World::getLayerTileSize(int layerNum) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return 16;
    }

    return layers[layerNum]->tilePane.getTileSize();
}

std::string World::getLayerTileSet(int layerNum) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return "";
    }

	string texName = getTextureManager()->getTextureNameByIndex(layers[layerNum]->tilePane.getTextureNum());

    return texName;
}

void World::setLayerFullbrightLights(int layerNum, bool enabled) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return;
    }

    layers[layerNum]->fullbrightLightsEnabled = enabled;

    forceEditorUpdateLayerList();
}

void World::setLayerLighting(int layerNum, bool enabled) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return;
    }

    layers[layerNum]->lightingEnabled = enabled;

    forceEditorUpdateLayerList();
}

bool World::getLayerFullbrightLights(int layerNum) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return false;
    }

    return layers[layerNum]->fullbrightLightsEnabled;
}

bool World::getLayerLighting(int layerNum) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return true;
    }
    
    return layers[layerNum]->lightingEnabled;
}

bool World::getLayerCombineWithPrevious(int layerNum) {
    
    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return false;
    }

    return layers[layerNum]->combineWithPreviousLayer;
}

void World::setLayerCombineWithPrevious(int layerNum, bool enabled) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return;
    }

    layers[layerNum]->combineWithPreviousLayer = enabled;
    
    forceEditorUpdateLayerList();
}

bool World::getLayerTilesAsLightMap(int layerNum) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return false;
    }

    return layers[layerNum]->tilesAreLightMap;
}

void World::setLayerTilesAsLightMap(int layerNum, bool enabled) {

    if(layerNum < 0 || layerNum >= int(layers.size())) {
        return;
    }

    layers[layerNum]->tilesAreLightMap = enabled;
}

// Layer Implementation.

World::Layer::Layer(void) {
    visible = true;
    cameraPosScale = 1;
    quadTree = new QuadTree(BoundingBox(-1024, 1024, 1024, -1024), WORLD_QUADTREE_DEPTH);
    drawBuffer = 0;
    lightBuffer = 0;
    ambientLight = Vector3D(1, 1, 1);

    lightingEnabled = true;
    fullbrightLightsEnabled = false;
    combineWithPreviousLayer = false;
    tilesAreLightMap = false;
}

World::Layer::~Layer(void) {

    delete quadTree;
    
    if(drawBuffer) {
        glDeleteTextures(1, &drawBuffer);
        drawBuffer = 0;
    }

    if(lightBuffer) {
        glDeleteTextures(1, &lightBuffer);
        lightBuffer = 0;
    }

}

