reorganize Terrain/PhysicsWorld/GameState/Engine to use NetworkClientConnect, and hence handle the connection process asynchronously, and finally properly implement receiving the terrain data from the server
authorTero Marttila <terom@fixme.fi>
Tue, 20 Jan 2009 23:30:18 +0200
changeset 408 e6cfc44266af
parent 407 443f6f7abcfb
child 409 1a03ff151abc
reorganize Terrain/PhysicsWorld/GameState/Engine to use NetworkClientConnect, and hence handle the connection process asynchronously, and finally properly implement receiving the terrain data from the server
src/Application.cc
src/Application.hh
src/Config.hh
src/Engine.cc
src/Engine.hh
src/GameState.cc
src/GameState.hh
src/Graphics.cc
src/Graphics.hh
src/Network/Client.cc
src/Network/Client.hh
src/Network/Server.cc
src/Network/Server.hh
src/PhysicsObject.cc
src/PhysicsWorld.cc
src/PhysicsWorld.hh
src/Player.cc
src/Projectile.cc
src/Rope.cc
src/Terrain.cc
src/Terrain.hh
--- a/src/Application.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Application.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -56,8 +56,8 @@
     arg_port = NETWORK_PORT_STR;
     arg_server = false;
     arg_connect = "";
-    arg_fullscreen = GRAPHICS_FULLSCREEN;
-    arg_resolution = PixelCoordinate(GRAPHICS_RESOLUTION_WIDTH, GRAPHICS_RESOLUTION_HEIGHT);
+    graphics.fullscreen = GRAPHICS_FULLSCREEN;
+    graphics.resolution = PixelCoordinate(GRAPHICS_RESOLUTION_WIDTH, GRAPHICS_RESOLUTION_HEIGHT);
 
     // extra state
     bool resolution_default = true;
@@ -93,14 +93,14 @@
                 break;
 
             case ARG_FULLSCREEN:
-                arg_fullscreen = true;
+                graphics.fullscreen = true;
                 
                 // choose best resolution unless explicitly set
                 if (resolution_default) {
                     const CL_DisplayMode best_mode = Graphics::getBestMode();
                     const CL_Size best_resolution = best_mode.get_resolution();
 
-                    arg_resolution = PixelCoordinate(best_resolution.width, best_resolution.height);
+                    graphics.resolution = PixelCoordinate(best_resolution.width, best_resolution.height);
                 }
 
                 break;
@@ -147,7 +147,7 @@
         throw ArgumentError("invalid format for --resolution");
     
     // store as PixelCoordinate
-    arg_resolution = PixelCoordinate(w, h);
+    graphics.resolution = PixelCoordinate(w, h);
 }
         
 void Main::dump_display_modes (void) {
@@ -183,7 +183,7 @@
         
         // setup graphics
         if (arg_graphics)
-            engine.setupGraphics(arg_resolution, arg_fullscreen);
+            engine.setupGraphics(graphics);
 
         // setup either network server, client or singleplayer
         if (arg_server) {
--- a/src/Application.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Application.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -41,14 +41,9 @@
         std::string arg_connect;
 
         /**
-         * --fullscreen
+         * --fullscreen and --resolution
          */
-        bool arg_fullscreen;
-
-        /**
-         * --resolution
-         */ 
-        PixelCoordinate arg_resolution;
+        GraphicsConfiguration graphics;
 
         /**
          * Set the arg_* members
--- a/src/Config.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Config.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -16,11 +16,16 @@
  */
 const float KG_PI = 3.14159265;
 
-// Physics simulation
-// Physics resolution
-const uint16_t MAP_WIDTH = 1000;
-const uint16_t MAP_HEIGHT = 800;
-const float MAP_SCALE = 1; // One "pixel" in "real" units
+/**
+ * Random generator seed to use for terrain generator
+ */
+const int TERRAIN_RANDOM_SEED = 1337;
+
+/**
+ * Terrain size, equal to physics simulation size
+ */
+const PixelDimension TERRAIN_WIDTH = 1000;
+const PixelDimension TERRAIN_HEIGHT = 800;
 
 /** Engine timeout, this determines our minimum tick rate */
 const TimeMS ENGINE_TIMEOUT_MS = 10;
--- a/src/Engine.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Engine.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -7,37 +7,78 @@
 #include <iostream>
 
 Engine::Engine (const std::string resource_xml_path) : 
+    terrain(NULL), game_state(NULL), graphics_config(NULL), graphics(NULL), net_server(NULL), net_client_connect(NULL),
     is_running(true), resources(resource_xml_path)
 {
     
 }
 
-void Engine::setupGraphics (PixelCoordinate resolution, bool fullscreen) {
+GameState& Engine::setupGame (Terrain *terrain) {
+    // ensure this isn't called in inappropriate ways
+    assert(!net_server);
+
+    // remember the terrain
+    this->terrain = terrain;
+
+    // create the GameState
+    game_state = new GameState(*terrain);
+    
+    // start graphics?
+    if (graphics_config)
+        startGraphics();
+    
+    return *game_state;
+}
+        
+GameState& Engine::setupGame (void) {
+    // proxy off to setupGame(Terrain *)
+    return setupGame(new Terrain(TERRAIN_WIDTH, TERRAIN_HEIGHT, TERRAIN_RANDOM_SEED));
+}
+
+void Engine::setupGraphics (const GraphicsConfiguration &config) {
+    // store config
+    graphics_config = &config;
+    
+    // start already?
+    if (game_state)
+        startGraphics();
+}
+
+void Engine::startGraphics (void) {
+    // check state
+    assert(game_state && graphics_config);
+
     // create the graphics
-    graphics = new Graphics(*this, game_state, resolution, fullscreen);
+    graphics = new Graphics(*this, *game_state, *graphics_config);
 }
 
 void Engine::setupNetworkServer (const std::string &listen_port) {
     NetworkEndpoint listen_addr(listen_port);
+    
+    // setup default game
+    setupGame();
 
     // create the server
-    net_server = new NetworkServer(game_state, listen_addr);
+    net_server = new NetworkServer(*game_state, listen_addr);
 }
 
 void Engine::setupNetworkClient (const std::string &connect_host, const std::string &connect_port) {
     // connect_to
     NetworkEndpoint connect_addr(connect_host, connect_port);
 
-    // create the client
-    net_client = new NetworkClient(*this, game_state, connect_addr);
+    // begin connecting, the client will callback us with setupGame once it's connected
+    net_client_connect = new NetworkClientConnect(*this, connect_addr);
 }
 
 void Engine::setupSinglePlayer (void) {
+    // setup default game
+    setupGame();
+
     // create player directly
- 	LocalPlayer* lp = new SinglePlayer(game_state);
+ 	LocalPlayer* lp = new SinglePlayer(*game_state);
 
     // add to gamestate
-	game_state.setLocalPlayer(lp);
+	game_state->setLocalPlayer(lp);
 }
 
 void Engine::stop (void) {
--- a/src/Engine.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Engine.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -17,14 +17,19 @@
 class Engine {
     private:
         // game state
-        GameState game_state;
+        Terrain *terrain;
+        GameState *game_state;
+
+        /** Set if setupGraphics has been called */
+        const GraphicsConfiguration *graphics_config;
         
         // Graphics/Input
         Graphics *graphics;
 
         // network server/client
         NetworkServer *net_server;
-        NetworkClient *net_client;
+        NetworkClientConnect *net_client_connect;
+        // XXX: currently unused: NetworkClient *net_client;
 
         // to exit the mainloop
         bool is_running;
@@ -36,8 +41,18 @@
         // default constructor
         Engine (const std::string resource_xml_path = RESOURCE_XML_PATH);
 
+        /**
+         * Setup game world using the given terrain, returning the new GameState
+         */
+        GameState& setupGame (Terrain *terrain);
+
+        /**
+         * Setup default game world using constants from Config.hh
+         */
+        GameState& setupGame (void);
+
         // setup graphics
-        void setupGraphics (PixelCoordinate resolution, bool fullscreen);
+        void setupGraphics (const GraphicsConfiguration &config);
         
         // set up network server/client
         // setting up both of these will lead to odd behaviour :)
@@ -45,11 +60,21 @@
         void setupNetworkClient (const std::string &connect_host, const std::string &connect_port);
 		void setupSinglePlayer (void);
         
-        // run the main loop
+        /**
+         * Run the game main loop. This will not return until the game aborts due to an error, or someone calls stop().
+         */
         void run (void);
+        
+        /**
+         * Terminate the main loop, causing run() to return once this loop iteration is finished
+         */
+        void stop (void);
 
-        // terminate the main loop
-        void stop (void);
+    private:
+        /**
+         * Actually start graphics, requires that game_state is now set
+         */
+        void startGraphics (void);
 
     public:
         // get a pointer to our resource manager
--- a/src/GameState.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/GameState.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -3,8 +3,10 @@
 #include "Engine.hh"
 #include "Config.hh"
 
-GameState::GameState (void) : 
-    world(Vector(0, MAP_GRAVITY), Vector(MAP_WIDTH, MAP_HEIGHT)), local_player(NULL), event_handler(NULL)
+GameState::GameState (Terrain &terrain) : 
+    world(Vector(0, MAP_GRAVITY), Vector(terrain.getWidth(), terrain.getHeight()), terrain), 
+    terrain(terrain),
+    local_player(NULL), event_handler(NULL)
 { 
 
 }
@@ -49,8 +51,8 @@
 }
     
 void GameState::draw(Graphics *g, PixelCoordinate camera, bool displayWeapon) {
-    // Draw world/terrain
-    world.draw(g, camera);
+    // Draw terrain
+    terrain.draw(g, camera);
 
     // Draw players
     for (std::list<Player*>::iterator it = player_list.begin(); it != player_list.end(); it++) {
--- a/src/GameState.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/GameState.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -25,9 +25,25 @@
 
 class GameState {
 public:
+    /**
+     * All active players
+     */
     std::list<Player*> player_list;
+
+    /**
+     * All active projectiles
+     */
     std::list<Projectile*> projectiles;
+
+    /**
+     * Our world simulation
+     */
     PhysicsWorld world;
+    
+    /**
+     * Our terrain
+     */
+    Terrain &terrain;
 
     /**
      * The one LocalPlayer that *we* control
@@ -42,11 +58,11 @@
 
 public:    
     /**
-     * ...
-     * 
-     * This should take some arguments
+     * Create the game world, using the given terrain
+     *
+     * @param terrain the world's terrain
      */
-    GameState (void);
+    GameState (Terrain &terrain);
 
     /**
      * Set event handler, only one can be set
--- a/src/Graphics.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Graphics.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -16,11 +16,11 @@
     );
 }
 
-Graphics::Graphics (Engine &engine, GameState &state, PixelCoordinate resolution, bool fullscreen) :
-    CL_DisplayWindow(GRAPHICS_WINDOW_TITLE, resolution.x, resolution.y, fullscreen, true),
+Graphics::Graphics (Engine &engine, GameState &state, const GraphicsConfiguration &config) :
+    CL_DisplayWindow(GRAPHICS_WINDOW_TITLE, config.resolution.x, config.resolution.y, config.fullscreen, true),
     engine(engine), 
     state(state), 
-    resolution(resolution),
+    resolution(config.resolution),
     fullscreen_resolution(0, 0), window_resolution(0, 0),
     update_timer(GRAPHICS_UPDATE_INTERVAL_MS),
     input(get_ic()->get_keyboard()),
@@ -42,7 +42,7 @@
     state.setEventHandler(this);
 
     // set correct resolution
-    if (fullscreen) {
+    if (config.fullscreen) {
         fullscreen_resolution = resolution;
 
     } else {
@@ -175,7 +175,7 @@
         PixelCoordinate target = player->getCoordinate() - PixelCoordinate(resolution.x / 2, (resolution.y - 100) / 2);
 
         // ...but keep the world in view
-        PixelCoordinate max = state.world.getDimensions() - resolution + PixelCoordinate(0, 100);
+        PixelCoordinate max = state.terrain.getDimensions() - resolution + PixelCoordinate(0, 100);
         
         // ...by limiting the value to 0...max
         camera = PixelCoordinate(
--- a/src/Graphics.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Graphics.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -2,6 +2,18 @@
 #define GRAPHICS_HH
 
 #include "GraphicsPointer.hh"
+#include "Types.hh"
+
+/** 
+ * Parameters used by Graphics
+ */
+struct GraphicsConfiguration {
+    /** Initial resolution to use */
+    PixelCoordinate resolution;
+    
+    /* Use fullscreen mode at startup */
+    bool fullscreen;
+}; 
 
 #include "GameState.hh"
 #include "Input.hh"
@@ -75,7 +87,7 @@
     /**
      *
      */
-    Graphics (Engine &engine, GameState &state, PixelCoordinate resolution, bool fullscreen);
+    Graphics (Engine &engine, GameState &state, const GraphicsConfiguration &config);
     
     /**
      * Returns a CL_Font that can be used for drawing text
--- a/src/Network/Client.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Network/Client.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -7,34 +7,39 @@
 
 #include <cassert>
 
-NetworkClient::NetworkClient (Engine &engine, GameState &state, const NetworkEndpoint &connect_to) : 
-    engine(engine), state(state), netsession(NETWORK_MAGIC_ID), server(netsession.connect(connect_to)),
-    controller(*this)
+/*
+ * NetworkClientConnect
+ */
+NetworkClientConnect::NetworkClientConnect (Engine &engine, const NetworkEndpoint &connect_to) :
+    engine(engine), netsession(NETWORK_MAGIC_ID)
 {
+    // connect NetworkSession to get server node (this is still blocking)
+    server = netsession.connect(connect_to);
+    
     // connect slots
-    slots.connect(netsession.sig_chan_message(NETCHAN_TERRAIN_ARRAY), this, &NetworkClient::on_terrain_array);
-    slots.connect(server->sig_disconnected(), this, &NetworkClient::on_disconnected);
+    slots.connect(netsession.sig_chan_message(NETCHAN_TERRAIN_ARRAY), this, &NetworkClientConnect::on_terrain_array);
+    slots.connect(server->sig_disconnected(), this, &NetworkClientConnect::on_disconnected);
+    
+    // then we must wait for the terrain data
 }
-        
-void NetworkClient::on_disconnected (void) {
+
+void NetworkClientConnect::on_disconnected (void) {
     Engine::log(ERROR, "client.on_disconnect") << "Disconnected from server";
     engine.stop();
 }
 
-void NetworkClient::on_terrain_array (NetworkPacketInput &pkt, NetworkNode *node) {
+void NetworkClientConnect::on_terrain_array (NetworkPacketInput &pkt, NetworkNode *node) {
     // ignore if not from server
     if (node != server)
         return;
-
-    Terrain &terrain = state.world;
-
+    
     // read map width/height
     PixelDimension map_w = pkt.read_uint32();
     PixelDimension map_h = pkt.read_uint32();
 
     // the terrain byte array
     size_t terrain_size = map_w * map_h;
-    uint8_t terrain_buf[map_w * map_h];
+    TerrainPixel *terrain_buf = new TerrainPixel[map_w * map_h];
 
     // read uncompressed terrain data
     size_t inflate_size = pkt.read_uncompressed(terrain_buf, terrain_size);
@@ -43,12 +48,29 @@
     if (inflate_size != terrain_size)
         throw Error("Corrupt terrain data");
 
-    // XXX: rework access
-    if (terrain.getDimensions() != PixelCoordinate(map_w, map_h))
-        throw Error("terrain is of the wrong size");
+    // create the terrain object that we then use, and hand over terrain_buf to it
+    Terrain *terrain = new Terrain(map_w, map_h, terrain_buf);
 
-    // load into terrain
-    terrain.loadFromBuffer(terrain_buf);
+    // execute connectDone
+    connectDone(terrain);
+}
+
+void NetworkClientConnect::connectDone (Terrain *terrain) {
+    // pass Terrain to engine to create game
+    GameState &gs = engine.setupGame(terrain);
+
+    // create our new NetworkClient object
+    client = new NetworkClient(engine, gs, netsession, server);
+}
+ 
+/*
+ * NetworkClient
+ */
+NetworkClient::NetworkClient (Engine &engine, GameState &state, NetworkSession &netsession, NetworkNode *server) : 
+    engine(engine), state(state), netsession(netsession), server(server),
+    controller(*this)
+{
+
 }
         
 void NetworkClient::player_quit (NetworkClientRemotePlayer *player) {
--- a/src/Network/Client.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Network/Client.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -8,6 +8,7 @@
  */
 
 // forward-declare
+class NetworkClientConnect;
 class NetworkClient;
 class NetworkClientLocalPlayer;
 class NetworkClientRemotePlayer;
@@ -27,7 +28,7 @@
          * The NetworkClient
          */
         NetworkClient &client;
-    
+        
     public:
         /**
          * Control objects on the given client using the client.netsession's NETCHAN_CORE channel
@@ -86,12 +87,10 @@
          */
         GameState &state;
 
-        CL_SlotContainer slots;
-        
         /**
          * The connect()-mode NetworkSession
          */
-        NetworkSession netsession;
+        NetworkSession &netsession;
 
         /**
          * The server NetworkNode from Netsession::connect
@@ -103,6 +102,8 @@
          */
         NetworkClientController controller;
 
+        CL_SlotContainer slots;
+
     public:
         /**
          * Create a NetworkClient with the given GameState, connecting a server on the given NetworkEndpoint
@@ -111,7 +112,48 @@
          * @param state the GameState to use
          * @param connect_to the address to connect to
          */
-        NetworkClient (Engine &engine, GameState &state, const NetworkEndpoint &connect_to);
+        NetworkClient (Engine &engine, GameState &state, NetworkSession &netsession, NetworkNode *server);
+    
+    public:
+        /**
+         * Called by NetworkClientRemotePlayer when they get disconnected. Doesn't do anything currently
+         */
+        void player_quit (NetworkClientRemotePlayer *player);
+};
+
+/**
+ * This handles the actual connection process to the server, and handles the initial data from the server. Once the
+ * Engine has a game running, we can create the actual NetworkClient object.
+ */
+class NetworkClientConnect {
+    protected:
+        /**
+         * The Engine we are running under
+         */
+        Engine &engine;
+
+        /**
+         * The NetworkSession we are using
+         */
+        NetworkSession netsession;
+
+        /**
+         * The server we've connected to
+         */
+        NetworkNode *server;
+
+        /**
+         * The NetworkClient that we eventually create
+         */
+        NetworkClient *client;
+        
+        CL_SlotContainer slots;
+
+    public:
+        /**
+         * Begin the connection process. Once it is complete, we will callback to Engine::networkClientConnected
+         */
+        NetworkClientConnect (Engine &engine, const NetworkEndpoint &connect_to);
     
     protected:
         /**
@@ -123,12 +165,11 @@
          * Receive the NETCHAN_TERRAIN_ARRAY message from the server and apply it to our GameState::world terrain
          */
         void on_terrain_array (NetworkPacketInput &pkt, NetworkNode *node);
-    
-    public:
+
         /**
-         * Called by NetworkClientRemotePlayer when they get disconnected. Doesn't do anything currently
+         * Finished connecting 
          */
-        void player_quit (NetworkClientRemotePlayer *player);
+        void connectDone (Terrain *terrain);
 };
 
 /**
--- a/src/Network/Server.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Network/Server.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -20,13 +20,56 @@
 }
 
 void NetworkServer::on_node_connected (NetworkNode *node) {
+    // send the terrain data
+    send_terrain_data(node);
+
     // create the player object (it logs it)
     NetworkServerPlayer *player = new NetworkServerPlayer(*this, node);
 
     // add to players
     players.push_back(player);
+
 }
-        
+ 
+void NetworkServer::send_terrain_data (NetworkNode *node) {
+    Terrain &terrain = state.world.terrain;
+    
+    // dimensions?
+    PixelCoordinate map = terrain.getDimensions();
+
+    // translate to a byte array
+    size_t terrain_size = map.x * map.y;
+    
+    // get terrain buffer
+    const uint8_t *terrain_buf = terrain.getTerrainBuffer();
+
+    // allocate our packet...
+    BigNetworkPacket pkt (
+        // NetworkChannel header
+        NETWORK_SESSION_HEADER_SIZE     
+
+        // our own header
+        + 2 * sizeof(uint32_t)          
+
+        // compressed terrain buffer
+        + NetworkPacketOutput::write_compressed_size(terrain_size)
+    );
+    
+    // write netsession header
+    node->write_packet_header(pkt, NETCHAN_TERRAIN_ARRAY);
+
+    // write terrain dimensions
+    pkt.write_uint32(map.x);
+    pkt.write_uint32(map.y);
+
+    // write compressed terrain data
+    pkt.write_compressed(terrain_buf, terrain_size);
+
+    // send
+    node->send_raw(pkt, true);
+}
+
+       
 void NetworkServer::handle_disconnect (NetworkServerPlayer *player) {
     // remove from list
     players.remove(player);
@@ -80,9 +123,6 @@
 
     // broadcast NETMSG_PLAYER_JOIN to all clients except current
     this->send_all_except(NETMSG_PLAYER_JOIN, hello_pkt, node, true);
-
-    // send terrain data...
-    send_terrain_data();
 }
 
 void NetworkServerPlayer::handleDig (Vector position, float radius) {
@@ -232,44 +272,6 @@
     send_position_update();
 }
         
-void NetworkServerPlayer::send_terrain_data (void) {
-    Terrain &terrain = server.state.world;
-    
-    // dimensions?
-    PixelCoordinate map = terrain.getDimensions();
-
-    // translate to a byte array
-    size_t terrain_size = map.x * map.y;
-    
-    // get terrain buffer
-    const uint8_t *terrain_buf = terrain.getTerrainBuffer();
-
-    // allocate our packet...
-    BigNetworkPacket pkt (
-        // NetworkChannel header
-        NETWORK_SESSION_HEADER_SIZE     
-
-        // our own header
-        + 2 * sizeof(uint32_t)          
-
-        // compressed terrain buffer
-        + NetworkPacketOutput::write_compressed_size(terrain_size)
-    );
-    
-    // write netsession header
-    node->write_packet_header(pkt, NETCHAN_TERRAIN_ARRAY);
-
-    // write terrain dimensions
-    pkt.write_uint32(map.x);
-    pkt.write_uint32(map.y);
-
-    // write compressed terrain data
-    pkt.write_compressed(terrain_buf, terrain_size);
-
-    // send
-    node->send_raw(pkt, true);
-}
-
 void NetworkServerPlayer::send_position_update (void) {
     NetworkPacket pkt;
     
--- a/src/Network/Server.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Network/Server.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -67,6 +67,12 @@
          * our list of players
          */
         void on_node_connected (NetworkNode *node);
+        
+        /**
+         * Called from on_node_connected to send the initial Terrain data using the NETCHAN_TERRAIN_ARRAY channel to
+         * the given node.
+         */
+        void send_terrain_data (NetworkNode *node);
 };
 
 /**
@@ -139,11 +145,6 @@
         void on_input (NetworkNode *node, NetworkPacketInput &pkt);
         
         /**
-         * Called from the constructor to send the initial Terrain data using the NETCHAN_TERRAIN_ARRAY channel.
-         */
-        void send_terrain_data (void);
-
-        /**
          * Called from on_input to broadcast an unreliable position update with this player's physics state
          */
         void send_position_update (void);
--- a/src/PhysicsObject.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/PhysicsObject.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -115,8 +115,8 @@
 }
 
 bool PhysicsObject::possibleLocation (Vector loc) {
-    for(unsigned int i = 0; i < this->shape.size(); i++) {
-        if(world.collides(loc+shape[i]))
+    for (unsigned int i = 0; i < this->shape.size(); i++) {
+        if (world.terrain.collides(loc+shape[i]))
             return false;
     }
     return true;
@@ -156,9 +156,9 @@
     // If the player has stopped and there's some ground under some of the 3 some of the 3t
     // set inAir false
     if (this->velocity == Vector(0,0)) {
-        this->inAir = !world.collides(this->position+shape[1]+Vector(0, 1))
-                      && !world.collides(this->position+shape[2]+Vector(0, 1))
-                      && !world.collides(this->position+shape[3]+Vector(0, 1));
+        this->inAir = !world.terrain.collides(this->position+shape[1]+Vector(0, 1))
+                      && !world.terrain.collides(this->position+shape[2]+Vector(0, 1))
+                      && !world.terrain.collides(this->position+shape[3]+Vector(0, 1));
         // If, however, there's a force caused by a bomb, e.g., set it in air.
         // Still, we have to be able to separate forces caused by walking attempts
         // and bombs etc (+0.1 because float comparison can be dangerous)
@@ -209,12 +209,12 @@
         reached += unitVector;
         // Check if any of the shapes points collide
         for (uint64_t i = 0; i < shape.size(); i++) {
-            if (world.collides(reached+shape[i])) {  // Collision
+            if (world.terrain.collides(reached+shape[i])) {  // Collision
 
                 collisionPoint = reached+shape[i];
 
                 if (inAir)
-                    this->bounce(world.getNormal(reached + shape[i], reached - unitVector + shape[i]));
+                    this->bounce(world.terrain.getNormal(reached + shape[i], reached - unitVector + shape[i]));
 
                 reached = reached - unitVector; // Return to last point
                 collided = true;
@@ -355,7 +355,7 @@
 }
     
 PixelCoordinate PhysicsObject::getCoordinate (void) const {
-    return world.getPixelCoordinate(position);
+    return world.terrain.getPixelCoordinate(position);
 }
 
 Vector PhysicsObject::getVelocity (void) const {
--- a/src/PhysicsWorld.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/PhysicsWorld.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -4,13 +4,13 @@
 
 #include <functional>
 
-PhysicsWorld::PhysicsWorld (Vector gravity, Vector dimensions) :
-    // XXX: assume Vector == PixelCoordinate
-    Terrain((unsigned int) dimensions.x, (unsigned int) dimensions.y, 1337), 
+PhysicsWorld::PhysicsWorld (Vector gravity, Vector dimensions, Terrain &terrain) :
+    terrain(terrain),
     dimensions(dimensions), 
     gravity(gravity),
     tick_timer(PHYSICS_TICK_MS)
 {
+    // wire up our timer
     slots.connect(tick_timer.sig_tick(), this, &PhysicsWorld::tick);
     tick_timer.start();
 }
--- a/src/PhysicsWorld.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/PhysicsWorld.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -18,36 +18,44 @@
 #include "Config.hh"
 
 /**
-* PhysicsWorld class. PhysicsWorld contains PhysicsObjects that are
-* simulated in the PhysicsWorld.
+* PhysicsWorld class. PhysicsWorld contains a number of PhysicsObjects that are simulated forward in time using
+* the physics tick rate
 */
-class PhysicsWorld : public Terrain {
+class PhysicsWorld {
     friend class PhysicsObject;
 
-private:
+// XXX: needs some fixing up, move Terrain methods
+public:   
+    /** The world's terrain */
+    Terrain &terrain;
 
+protected:
+    /** List of simulated objects*/
+    std::list<PhysicsObject*> objects;
     
-protected:
-    std::list<PhysicsObject*> objects;
+    /** Size of simulation area */
+    Vector dimensions;
 
-    // Contains connections between signals and slots
+    /** Gravity vector */
+    Vector gravity;
+    
+    /** Physics simulation ticks */
+    Timer tick_timer;
+    
     CL_SlotContainer slots;
 
-    Vector dimensions;
-    Vector gravity;
-
 public:
-    // Someone is going to kill me for this
-    Timer tick_timer;
-
-    PhysicsWorld(Vector gravity, Vector dimensions);
+    /**
+     * Construct a world with the given configuration. 
+     */
+    PhysicsWorld (Vector gravity, Vector dimensions, Terrain &terrain);
 
     /**
      * Add object to the PhysicsWorld.
      *
      * @param object Pointer to the PhysicsObject to add.
      */
-    void addPhysicsObject(PhysicsObject *object);
+    void addPhysicsObject (PhysicsObject *object);
     
     /**
      * Remove the object from the PhysicsWorld.
--- a/src/Player.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Player.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -63,7 +63,7 @@
     
 void Player::spawn (Vector position) {
     // dig hole
-    world.removeGround(position, PLAYER_DIG_RADIUS);
+    world.terrain.removeGround(position, PLAYER_DIG_RADIUS);
 
     // update position
     setPosition(position);
@@ -108,7 +108,7 @@
     Vector digPosition = pos + getDirection() * PROJECTILE_START_DISTANCE;
     
     // remove directly
-    world.removeGround(digPosition, radius);
+    world.terrain.removeGround(digPosition, radius);
 }
 
 void Player::handleFireWeapon (Weapon *weapon, Vector position, Vector velocity) {
@@ -363,7 +363,7 @@
     );
     
     // draw a proper crosshair-box
-    PixelCoordinate crosshair = getCoordinate() + world.getPixelCoordinate(getDirection() * PLAYER_CROSSHAIR_DISTANCE) - camera;
+    PixelCoordinate crosshair = getCoordinate() + world.terrain.getPixelCoordinate(getDirection() * PLAYER_CROSSHAIR_DISTANCE) - camera;
     
     gc->draw_rect(
         CL_Rectf(
--- a/src/Projectile.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Projectile.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -10,7 +10,7 @@
     weapon(weapon)
 {
     // set birth tick
-    birth_tick = world.tick_timer.get_ticks();
+    birth_tick = world.getTicks();
 
     // XXX: projectiles should be particles?
     std::vector<Vector> shape(4);
@@ -32,7 +32,7 @@
  
 void Projectile::onDestroy (Vector position, bool removeGround) {
     if (removeGround)
-        world.removeGround(position, weapon->getExplosionRadius());
+        world.terrain.removeGround(position, weapon->getExplosionRadius());
 
     destroy();
 }
@@ -68,7 +68,7 @@
    
 void Projectile::tick (TimeMS dt) {
     // expire projectiles
-    if (world.tick_timer.get_ticks() > birth_tick + weapon->getExpire())
+    if (world.getTicks() > birth_tick + weapon->getExpire())
         onDestroy(position, true);
 
     // super
--- a/src/Rope.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Rope.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -193,7 +193,7 @@
             return;
 
         // If there's no ground on the pivot point anymore, release the rope
-        if (!world.collides(position)) { 
+        if (!world.terrain.collides(position)) { 
             // XXX: move to some new method
             state = ROPE_FLYING;
             length = ROPE_LENGTH;
--- a/src/Terrain.cc	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Terrain.cc	Tue Jan 20 23:30:18 2009 +0200
@@ -25,13 +25,20 @@
 
 Terrain::Terrain (PixelDimension width, PixelDimension height, int seed) :
     terrain_buf(NULL),
-    width(width), 
-    height(height)
+    width(width), height(height)
 {
     // allocate+generate random terrain
     generateTerrain(seed);
 }
 
+Terrain::Terrain (PixelDimension width, PixelDimension height, TerrainPixel *terrain_buf) :
+    terrain_buf(terrain_buf),
+    width(width), height(height)
+{
+    // just generate the pixel buffer
+    generatePixelBuffer();
+}
+
 Terrain::~Terrain (void) {
     // free terrain data
     delete[] terrain_buf;
@@ -346,8 +353,9 @@
 }
 
 /**
- * Gets the index of the given coordinate direction
- * referring to the DIRECTIONS table in Physics.hh
+ * Gets the index of the given coordinate direction referring to the DIRECTIONS table in Physics.hh
+ *
+ * XXX: ugly little "lookup table"
  */
 static int getDirectionIndex (Vector direction) {
     Vector dir = direction.roundToInt();
@@ -375,32 +383,35 @@
 }
 
 Vector Terrain::getNormal(Vector point, Vector prevPoint) const {
-    // XXX: cleanup
+    // convert location to coordinate
     PixelCoordinate p = getPixelCoordinate(point);
-
+    
+    // sanity check
     assert(point != prevPoint);
-
-    Vector normal(0, 0);
-
-    // These two must be rounded separately
+    
+    // round and subtract to get an integer direction vector, and turn this into a direction index
     int dirIdx = getDirectionIndex(prevPoint.roundToInt() - point.roundToInt());
-
-    normal += DIRECTIONS[dirIdx];
-
+    
+    // always add our own direction to normal
+    Vector normal = DIRECTIONS[dirIdx];
+    
+    // check the two pixels clockwise from the impact direction
     for (int i = 1; i <= 2; i++) {
         if (getType(point + DIRECTIONS[(dirIdx + i + 8) % 8]) == TERRAIN_EMPTY) {
             normal += DIRECTIONS[(dirIdx + i + 8) % 8];
         }
     }
-
+    
+    // check the two pixels counterclockwise from the impact direction
     for (int i = 1; i <= 2; i++) {
         if (getType(point + DIRECTIONS[(dirIdx - i + 8) % 8]) == TERRAIN_EMPTY) {
             normal += DIRECTIONS[(dirIdx - i + 8) % 8];
         }
     }
-
+    
+    // sanity check
     if (getType(point) == TERRAIN_EMPTY || getType(prevPoint) != TERRAIN_EMPTY) {
-        Engine::log(DEBUG, "Physics.getNormal ") << "logic ground error";
+        Engine::log(DEBUG, "Physics.getNormal ") << "silly collision";
     }
     
     return normal;
--- a/src/Terrain.hh	Tue Jan 20 23:24:04 2009 +0200
+++ b/src/Terrain.hh	Tue Jan 20 23:30:18 2009 +0200
@@ -39,7 +39,7 @@
  */ 
 class Terrain {
 protected:
-    /** The terrain data is stored as a linear array in row-major order, with width*height elements */
+    /** The terrain data is stored as a linear array in row-major order, with width * height elements */
     TerrainPixel *terrain_buf;
 
     /** Terrain dimensions */
@@ -51,22 +51,35 @@
     // XXX: terrain texture
     std::vector<std::vector<int> > texture;
 
+public:    
     /**
      * Default constructor. The width/height are set to zero and the terrain is invalid until it gets updated
      */
     Terrain (void);
 
     /**
-     * Constructor.
+     * Construct a randomly generated terrain
      *
      * @param width terrain width
      * @param height terrain height
-     * @param seed andom number generator seed used to generate the random terrain.
+     * @param seed random number generator seed used to generate the random terrain.
      */
     Terrain (PixelDimension width, PixelDimension height, int seed);
 
     /**
-     * Destructor
+     * Construct the terrain using the provided terrain data. The given \a terrain_buf must be a linear array in the
+     * same format as Terrain::terrain_buf, meaning a row-major order array with width * height elements. The buffer
+     * must be allocated on the heap using 'new []', and ownership will be transferred (i.e. the buffer is not copied,
+     * and will eventually be delete []'d).
+     *
+     * @param width terrain width
+     * @param height terrain height
+     * @param terrain_buf dynamically allocated width * height array of terrain data
+     */
+    Terrain (PixelDimension width, PixelDimension height, TerrainPixel *terrain_buf);
+
+    /**
+     * Destructor, frees our terrain buffer
      */
     ~Terrain (void);
 
@@ -135,11 +148,22 @@
     /**
      * Return the terrain dimensions à la a PixelCoordinate
      */
-    inline PixelCoordinate getDimensions (void) const {
+    PixelCoordinate getDimensions (void) const {
         return PixelCoordinate(width, height);
     }
 
     /**
+     * Return dimensions in component form
+     */
+    PixelDimension getWidth (void) const {
+        return width;
+    }
+
+    PixelDimension getHeight (void) const {
+        return height;
+    }
+
+    /**
      * Return the type of terrain at given position. Returns TERRAIN_ROCK if given point is not inside terrain area.
      *
      * @param x terrain x coordinate