/*
 * client/DungeonScreen.cc:
 *
 * Copyright (C) 2000 John Watson, jwatson@tempusmud.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.
 * 
 */

#include "DungeonScreen.h"
#include "ClientLightmap.h"
#include "ClientSector.h"
#include "ClientSide.h"
#include "ClientSound.h"
#include "ClientSprite.h"
#include "ClientState.h"

#include "../server/Player.h"
#include "../server/Zone.h"
#include "../server/LineDef.h"
#include "../server/SideDef.h"
#include "../server/Sector.h"
#include "../server/SectorSpecial.h"

#include "../server/Sound.h"
#include "../server/Lightmap.h"
#include "../server/Projectile.h"
#include "../server/Sprite.h"

#include "../widgets/WidgetManager.h"
#include "../util/TextureManager.h"
#include "../util/ModelManager.h"
#include "../util/VMath.h"

#include "../util/md2_model.h"

#include "../gl_stubs.h"
#include "../GraphicsState.h"

#include "../server/Camera.h"
#include "../util/TextureFont.h"

#include "Renderer.h"
#include "Renderer_multitex.h"
#include "Renderer_default.h"

#include <signal.h>
#include <math.h>

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

#ifndef M_PI
#define M_PI           3.14159265358979323846  /* pi */
#endif

TextureObject * weapon_textures[10];
md2_model_t * weapon_models[10];
int cur_weapon = 1;

float last_pos[3] = { 0, 0, 0 };

Mix_Chunk * footstep_wavs[4] = { 0, 0, 0, 0 };
Mix_Chunk * turn_wav = 0;
Mix_Chunk * pain_fall_wav = 0;
Mix_Chunk * pain2_wav = 0;
Mix_Chunk * explode_wav = 0;
Mix_Chunk * fire_wav = 0;
Mix_Chunk * slash_wav = 0;
Mix_Chunk * jump_wav = 0;
Mix_Chunk * lift_move_wav = 0;
Mix_Chunk * lift_stop_wav = 0;

static Mix_Chunk * pickup_wav = 0;
static Mix_Chunk * nopass_magic_wav = 0;

static Mix_Chunk * lava_wav = 0;
static int lava_channel = 0;

int headbob = 0;
int headbob_delta = 3;
int headbob_max   = 5;
int headbob_cur   = 0;
int cur_footstep  = 0;
int num_footsteps = 4;
int footstep_map[] = { 0, 1, 2, 3,
                       1, 0, 3, 2,
                       0, 2, 3, 1 };
int num_mapped_footsteps = 12;
        
static bool draw_invisible_sectors = false;
static bool draw_map_overlay = false;
static bool draw_weapon = true;
static const int MAP_ZOOM_BASE = 16;//
static int  map_zoom_factor = MAP_ZOOM_BASE/4;//

static GLuint dummy_texture = 0;
static GLuint automap_playerdisc_list = 0;

class Zone * cur_zone = 0;

static class Renderer * _renderer = 0;

static void my_gluPerspective( GLdouble fovy, GLdouble aspect,
                               GLdouble zNear, GLdouble zFar )
{
        GLdouble xmin, xmax, ymin, ymax;
        
        ymax = zNear * tan( fovy * M_PI / 360.0 );
        ymin = -ymax;
        
        xmin = ymin * aspect;
        xmax = ymax * aspect;
        
        glFrustum( xmin, xmax, ymin, ymax, zNear, zFar );
}



static void drawWalls( const class Zone * zone,
                       const class Player * player,
                       const class Sector * pSect ) {
        
        const Array<class LineDef *> & lines = zone->getLines();
        
        if ( ! cl_state.use_pvs || ! zone->isVisLoaded() )
                pSect = 0;
        
        Array< class LineDef * > drawLines( pSect ? 
                                            pSect->_visibleLines.length :
                                            ( lines.length ) );
        if ( pSect ) {
                for ( int i = 0; i < pSect->_visibleLines.length; ++i ) {
                        drawLines[i] = lines[ pSect->_visibleLines[ i ] ];

                        if ( drawLines[i]->modified ) {
                                class SideDef ** sides = drawLines[i]->sides;
                                
                                sides[0]->clientSide->adjustHeights( sides[0], sides[1] );
                                if ( sides[1] )
                                        sides[1]->clientSide->adjustHeights( sides[1], sides[0] );
                                drawLines[i]->modified = false;
                        }
                }
        }

        else {
                for ( int j = 0; j < lines.length; ++j ) {
                        drawLines[ j ] = lines[ j ];
                        
                        if ( drawLines[j]->modified ) {
                                class SideDef ** sides = drawLines[j]->sides;
                                
                                sides[0]->clientSide->adjustHeights( sides[0], sides[1] );
                                if ( sides[1] )
                                        sides[1]->clientSide->adjustHeights( sides[1], sides[0] );
                                drawLines[j]->modified = false;
                        }
                }
        }

        _renderer->drawWalls( zone, player, pSect, drawLines );
}



static void drawZoneMap( const class Player * player ) {

        const class Zone * zone = player->getContainerZone();
                
        const Array<Sector *> & sectors = zone->getSectors();
        
        const float sect_color[4] = { 1, 1, 1, 0.1 };

        glColor4fv( sect_color );
        
        for ( int i = 0; i < sectors.length; ++i ) { 

                if ( sectors[i]->spec )
                        glColor4f( 1, 0.5, 1, 0.2 );
                
                glBegin( GL_POLYGON );
                {
                        for ( int j = sectors[i]->verts.length-1; j >= 0; --j ) {
                                glVertex3f( sectors[i]->verts[j][0], 
                                            sectors[i]->verts[j][1], 0 );
                        }
                }
                glEnd();

                if ( sectors[i]->spec )
                        glColor4fv( sect_color );

        }
        const Array<class LineDef *> & lines = zone->getLines();

        glDisable( GL_BLEND );

        glBegin( GL_LINES );
        {
                for ( int i = 0; i < lines.length; ++i ) {
                        // 2-sided line
                        if ( lines[i]->sides[1] ) {
                                if ( lines[i]->script )
                                        glColor3f( 0.1, 1.0, 0.3 );
                                else if ( lines[i]->sides[0]->sector->getFloorHeight() ==
                                          lines[i]->sides[1]->sector->getFloorHeight() &&
                                          lines[i]->sides[0]->sector->spec == 0 &&
                                          lines[i]->sides[1]->sector->spec == 0 ) {

                                        continue;
                                }
                                else {
                                        glColor3f( 0.5, 0.5, 0.5 );
                                }
                        }
                        else {
                                glColor3f( 1, 1, 1 );
                        }
                
                        glVertex2fv( lines[i]->verts[0] );
                        glVertex2fv( lines[i]->verts[1] );
                }
        }
        glEnd();

}

static void drawZone( const class Player * player ) {
                    
        const class Zone * zone = player->getContainerZone();
        
        g_state.num_surfs = 0;
        g_state.num_ents = 0;

        _renderer->setup();
        
        const class Sector * pSect = player->getSector();
        
        /*
        static const class Sector * last_psect = 0;
        if ( pSect != last_psect ) {
                cout << " pSect: num:" << pSect->_num
                     << ", num vis lines/mylines:" << pSect->_visibleLines.length
                     << "/" << pSect->_myLines.length << endl;
                last_psect = pSect;
        }
        */

        if ( cl_state.draw_floors )
                _renderer->drawSectors( zone, player, pSect );
        
        if ( cl_state.draw_walls )
                drawWalls( zone, player, pSect );

        _renderer->cleanup();

}

static void drawDisc( const float radius, float r, float g, float b ) {
        
        const int NUM_VERTS = 9;
        
        float verts[ NUM_VERTS + 1 ][2]; 
        double angle = 0;

        for ( int i = 0; i < NUM_VERTS; ++i ) {
                angle = i * M_PI * 2 / NUM_VERTS;
                verts[i][0] = cos( angle ) * radius;
                verts[i][1] = -sin( angle ) * radius;
        }

        verts[ NUM_VERTS ][0] = verts[0][0];
        verts[ NUM_VERTS ][1] = verts[0][1];
        
        glBegin( GL_TRIANGLE_FAN );
        {
                glColor3f( r/2, g/2, b/2 );
                
                glVertex2f( 0, 0 );

                glColor3f( r, g, b );

                for ( int i = NUM_VERTS; i >=0; --i ) {
                        glVertex2f( verts[i][0], verts[i][1] );
                }
        }
        glEnd();

}

static void drawBounding( const class Entity * ent, bool reverse, float r, float g, float b ) {

        int radius = ent->getBoundingRadius();
        int height = ent->getHeight();
        
        const int MAX_NUM_VERTS = 16;
        
        int NUM_VERTS = 16;
        
        float verts[ MAX_NUM_VERTS + 1 ][2];
        double angle = 0;
        
        if ( reverse ) {
                for ( int i = 0; i < NUM_VERTS; ++i ) {
                        angle = i * M_PI * 2 / NUM_VERTS;
                        verts[i][0] = cos( angle ) * radius;
                        verts[i][1] = sin( angle ) * radius;
                }
        }
        else {
                for ( int i = 0; i < NUM_VERTS; ++i ) {
                        angle = i * M_PI * 2 / NUM_VERTS;
                        verts[i][0] = cos( angle ) * radius;
                        verts[i][1] = -sin( angle ) * radius;
                }

        }

        verts[ NUM_VERTS ][0] = verts[0][0];
        verts[ NUM_VERTS ][1] = verts[0][1];
                
        float dark[3] = {
                r / 2,
                g / 2,
                b / 2
        };
        
        glColor3f( r, g, b );

        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

        glBegin( GL_QUAD_STRIP );
        {
                for ( int i = 0; i <= NUM_VERTS ; ++i ) {
                        
                        glVertex3f( verts[i][0], verts[i][1], 0 );
                        glVertex3f( verts[i][0], verts[i][1], height );
                }
        }
        glEnd();

        //
        // top
        //


        glColor3fv( dark );

        glBegin( GL_TRIANGLE_FAN );
        {
                glVertex3f( 0, 0, height );

                for ( int i = NUM_VERTS; i >=0; --i ) {
                        glVertex3f( verts[i][0], verts[i][1], height );
                }
        }
        glEnd();

        //
        // bottom
        //
        
        glBegin( GL_TRIANGLE_FAN );
        {
                glVertex3f( 0, 0, 0 );

                for ( int i = 0; i <= NUM_VERTS; ++i ) {
                        glVertex3f( verts[i][0], verts[i][1], 0 );
                }
        }
        glEnd();
        
        float theta = VMath::degrees2radians( ent->getTilt() );

        glColor3f( 1, 0, 1 );
        glBegin( GL_LINES );
        {
                glVertex3f( 0, 0, height / 2 );
                glVertex3f( radius + 50 , 0, height / 2 + ( radius + 50 ) * sin( theta ) );
        }
        glEnd();

}


static GLuint generateBounding( const class Entity * ent, bool reverse, float r, float g, float b ) {

        GLuint alist = glGenLists( 1 );
           
        if ( alist == 0 ) {
                cout << "ERROR" << endl;
                return 0;
        }

        glNewList( alist, GL_COMPILE );
        {
                drawBounding( ent, reverse, r, g, b );
        }
        glEndList();

        return alist;
}


DungeonScreen::DungeonScreen( int w, int h, Client::State escape, Client::State close )
        : GenericScreen( w, h, escape, close ),
          _playerList(0)
{
        _name = "DungeonScreen";

        if ( g_state.use_multitex )
                _renderer = new Renderer_multitex();
        else
                _renderer = new Renderer_default();

        weapon_models[0] = ModelManager::getModel( "pknight/flamesword" );
        weapon_models[1] = ModelManager::getModel( "pknight/axe" );
        weapon_models[2] = ModelManager::getModel( "pknight/battleaxe" );
        weapon_models[3] = ModelManager::getModel( "pknight/bastard_sword" );
        weapon_models[4] = ModelManager::getModel( "pknight/split_sword" );
        weapon_models[5] = ModelManager::getModel( "pknight/morning_star" );
        weapon_models[6] = ModelManager::getModel( "pknight/spiked_flail" );
        weapon_models[7] = ModelManager::getModel( "pknight/super_flamesword" );
        weapon_models[8] = ModelManager::getModel( "pknight/icesword" );
        weapon_models[9] = ModelManager::getModel( "pknight/lightning_sword" );


        setUpdateOk( false );

        //
        // obtain the dummy texture
        // 

        dummy_texture = TextureManager::getDummyTexture();
        
        Client::getWidgetManager()->addUpdateWidget( (class Widget *) this );

        if ( ! footstep_wavs[0] )
                footstep_wavs[0] = Mix_LoadWAV( "lib/sfx/footstep1.wav" );
        if ( ! footstep_wavs[1] )
                footstep_wavs[1] = Mix_LoadWAV( "lib/sfx/footstep2.wav" );
        if ( ! footstep_wavs[2] )
                footstep_wavs[2] = Mix_LoadWAV( "lib/sfx/footstep3.wav" );
        if ( ! footstep_wavs[3] )
                footstep_wavs[3] = Mix_LoadWAV( "lib/sfx/footstep4.wav" );
        
        if ( ! turn_wav )
                turn_wav = Mix_LoadWAV( "lib/sfx/jump_land.wav" );

        if ( ! pain_fall_wav )
                pain_fall_wav = Mix_LoadWAV( "lib/sfx/pain_fall.wav" );

        if ( ! pain2_wav )
                pain2_wav = Mix_LoadWAV( "lib/sfx/pain3.wav" );

        if ( ! explode_wav )
                explode_wav = Mix_LoadWAV( "lib/sfx/big_explode.wav" );

        if ( ! fire_wav )
                fire_wav = Mix_LoadWAV( "lib/sfx/fireloop.wav" );

        if ( ! slash_wav )
                slash_wav = Mix_LoadWAV( "lib/sfx/sword_slash1.wav" );

        if ( ! jump_wav )
                jump_wav = Mix_LoadWAV( "lib/sfx/player_jump1.wav" );

        if ( ! lift_move_wav )
                lift_move_wav = Mix_LoadWAV( "lib/sfx/lift_move.wav" );

        if ( ! lift_stop_wav )
                lift_stop_wav = Mix_LoadWAV( "lib/sfx/lift_stop.wav" );

        if ( ! pickup_wav )
                pickup_wav = Mix_LoadWAV( "lib/sfx/pickup.wav" );

        if ( ! nopass_magic_wav )
                nopass_magic_wav = Mix_LoadWAV( "lib/sfx/nopass_magic.wav" );
        
        if ( ! lava_wav )
                lava_wav = Mix_LoadWAV( "lib/sfx/lavaloop_4.wav" );
        
       //------------------------------------------

}

void DungeonScreen::initZone( class Player * player, class Zone * zone ) {
        
        Uint32 tick_start = SDL_GetTicks();

        cur_zone = zone;

        player->camera->angles[2] = VMath::clampAngle360( player->getAngle() );

        //        TextureManager::purge();
        
        weapon_textures[0] = TextureManager::getTexture( "models/pknight/flamesword", GL_RGB );
        weapon_textures[1] = TextureManager::getTexture( "models/pknight/axe", GL_RGB );
        weapon_textures[2] = TextureManager::getTexture( "models/pknight/battleaxe", GL_RGB );
        weapon_textures[3] = TextureManager::getTexture( "models/pknight/bastard_sword", GL_RGB );
        weapon_textures[4] = TextureManager::getTexture( "models/pknight/split_sword", GL_RGB );
        weapon_textures[5] = TextureManager::getTexture( "models/pknight/morning_star", GL_RGB );
        weapon_textures[6] = TextureManager::getTexture( "models/pknight/spiked_flail", GL_RGB );
        weapon_textures[7] = TextureManager::getTexture( "models/pknight/super_flamesword", GL_RGB );
        weapon_textures[8] = TextureManager::getTexture( "models/pknight/icesword", GL_RGB );
        weapon_textures[9] = TextureManager::getTexture( "models/pknight/lightning_sword", GL_RGB );

        //
        // preload textures
        //

        const class Array<const char*> & texnames = zone->getTexnames();
        
        cout << " ** PRELOADING " << texnames.length  << " textures" << endl;

        for ( int i = 1; i < texnames.length; ++i ) {
                TextureManager::getTexture( texnames[i],   GL_RGB );
        }
        
        // create lightmaps
        
        const class Array<class Lightmap *> & lms = zone->getLightmaps();
        
        if ( lms.length == 0 ) {
                cl_state.use_lightmaps = false;
        }
        else {
                for ( int i = 0; i < lms.length; ++i ) {
                        ClientLightmap * clm = new ClientLightmap( lms[i] );
                        lms[i]->clientLightmap = clm;
                }
        }

        // create sector data

        const Array<Sector *> & sectors = zone->getSectors();
        
        for ( int i = 0; i < sectors.length; ++i ) {
                ClientSector *cs = new ClientSector( sectors[i] );
                sectors[i]->clientSector = cs;
        }
        
        // create sidedef data

        const Array<SideDef *> & sides = zone->getSides();

        for ( int i = 0; i < sides.length; ++i ) {
                ClientSide *cs = new ClientSide( sides[i] );
                sides[i]->clientSide = cs;
        }

        // load any needed sprites

        const Vector<class Entity *> & ents = zone->getContents();
        
        for ( Vector< class Entity * >::const_iterator iter = ents.begin();
              iter != ents.end(); ++iter ) { 
                const class Sprite * sprite = (*iter)->getSprite();
                if ( sprite ) {
                        sprite->clientSprite = new ClientSprite( sprite );
                }
        }        
        
        // create random gl stuff
        
        _playerList = generateBounding( player, false, 1, 1, 0 );
        
        automap_playerdisc_list   = glGenLists( 1 );
        
        //
        // THIS IS A ONE-TIME LIST CREATION, DO NOT DELETE IN unloadZone()
        //
        
        glNewList( automap_playerdisc_list, GL_COMPILE );
        {
                glShadeModel( GL_FLAT );
                drawDisc( player->getBoundingRadius(), 1, 1, 0 );
        }
        glEndList();
        
        Uint32 tick_end = SDL_GetTicks();

        // make sure messages dont get lost in the zone transition

        if ( player->camera && player->camera->msg ) {
                player->camera->msg_timeout += ( tick_end - tick_start );
        }
                
        cout << " init done" << endl;
}

void DungeonScreen::unloadZone( class Player * player, class Zone * zone ) {
        
        cur_zone = 0;

        if ( _playerList != 0 ) {
                glDeleteLists( _playerList, 1 );
                _playerList = 0;

                // TODO: add support for saving and reloading current zone state from disk,
                // to free memory while the zone is not in use
        }
        
        // unload textures

        //        TextureManager::purge();

        // unload client lightmaps

        const class Array<class Lightmap *> & lms = zone->getLightmaps();
        
        for ( int i = 0; i < lms.length; ++i ) {
                if ( lms[i]->clientLightmap ) {
                        delete lms[i]->clientLightmap;
                        lms[i]->clientLightmap = 0;
                }
        }

        // unload sector data

        const Array<Sector *> & sectors = zone->getSectors();

        for ( int i = 0; i < sectors.length; ++i ) {
                if ( sectors[i]->clientSector ) {
                        delete sectors[i]->clientSector;
                        sectors[i]->clientSector = 0;
                }
        }
        
        // unload sidedef data

        const Array<SideDef *> & sides = zone->getSides();

        for ( int i = 0; i < sides.length; ++i ) {
                if ( sides[i]->clientSide ) {
                        delete sides[i]->clientSide;
                        sides[i]->clientSide = 0;
                }
        }

        // unload any needed sprites

        const Vector<class Entity *> & ents = player->getContainerZone()->getContents();
        
        for ( Vector< class Entity * >::const_iterator iter = ents.begin();
              iter != ents.end(); ++iter ) { 
                const class Sprite * sprite = (*iter)->getSprite();
                if ( sprite && sprite->clientSprite ) {
                        delete sprite->clientSprite;
                        sprite->clientSprite = 0;
                }
        }        
        
        // unload random gl stuff
        
        if ( _playerList != 0 ) {
                glDeleteLists( _playerList, 1 );
                _playerList = 0;
        }  
}

DungeonScreen::~DungeonScreen() {
        
        if ( _playerList ) {
                glDeleteLists( _playerList, 1 );
                _playerList = 0;
        }
        
        Client::getWidgetManager()->removeUpdateWidget( (class Widget *) this );
}

void drawModel( int frame_num, md2_model_t & model, const class TextureObject * tex, bool opaque ) {
        
        if ( model.isOk == false )
                return;

        if ( frame_num > model.numFrames ) {
                cout << " :: WARNING: frame " << frame_num << " out of range for " << model << endl;
                frame_num = 0;
        }

        if ( g_state.use_multitex ) {
                glActiveTextureARB_ptr( GL_TEXTURE0_ARB );
        }
        
        glEnable( GL_TEXTURE_2D );

        glBindTexture( GL_TEXTURE_2D, tex->getIndex() );
        
        if ( opaque ) {
                glDisable( GL_BLEND );
                glDisable( GL_ALPHA_TEST );
        }
        else { 
                glEnable( GL_BLEND );
                glEnable( GL_ALPHA_TEST );
                glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        }

        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
        
        glTexParameterf( GL_TEXTURE_2D,
                         GL_TEXTURE_MIN_FILTER,
                         GL_NEAREST );

        glFrontFace( GL_CW );

        for ( int i = 0; i < model.numGLCommands; ++i ) {
                glBegin( (GLenum) model.gl_cmds[i].gl_type );
                
                float * tc = model.gl_cmds[i].texcoords;
                
                for ( int j = 0; j < model.gl_cmds[i].num_verts; ++j ) {
                        glTexCoord2fv( tc );
                        glVertex3fv(& model.frames[frame_num].verts[ model.gl_cmds[i].verts_i[j] * 3 ] );
                        tc+=2;
                }
                glEnd();
        }

        glDisable( GL_TEXTURE_2D );
        glDisable( GL_BLEND );
        glDisable( GL_ALPHA_TEST );

        glFrontFace( GL_CCW );

}

static void showSprite( const class Character * viewer, 
                        const class Entity * target ) {
        
        const class Sprite * sprite = target->getSprite();
        const class ClientSprite * cs = sprite->clientSprite;
        
        TextureObject * to = cs->texs[ sprite->cur_frame ];
        
        float vect[3] = { 
                target->getPos()[0] - viewer->getPos()[0],
                target->getPos()[1] - viewer->getPos()[1],
                target->getPos()[2] + target->getHeight()/2 - viewer->getPos()[2] - viewer->getEyeLevel()
        };
        
        float angle_z = VMath::radians2degrees( atan2( vect[1], vect[0] ) );
        float dist = VMath::length2d( vect );
        float angle_y = VMath::radians2degrees( atan2( vect[2], dist ) );
        float halfHeight = ((float)target->getHeight())/2;
        
        glTranslatef( 0, 0, target->getHeight()/2 );
        glRotatef( angle_z, 0, 0, 1 );
        glRotatef( angle_y + 90, 0, -1, 0 );

        /*
        static int rot = 0;
        cout << "rot=" << rot << endl;
        glRotatef( ++rot, 0, 0, 1 );
        */

        glRotatef( 90, 0, 0, -1 );

        glColor4f( 1, 1, 1, 1 );
        glDepthMask( GL_FALSE );
        glBindTexture( GL_TEXTURE_2D, to->getIndex() );
        glEnable( GL_TEXTURE_2D );
        glEnable( GL_BLEND );
        glEnable( GL_ALPHA_TEST );

        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

        glBegin( GL_QUADS );
        {
                glTexCoord2f( 0, 0 );
                glVertex2f(  -target->getBoundingRadius(), -halfHeight );
                glTexCoord2f( 1, 0 );
                glVertex2f( target->getBoundingRadius(), -halfHeight );
                glTexCoord2f( 1, 1 );
                glVertex2f( target->getBoundingRadius(), halfHeight );
                glTexCoord2f( 0, 1 );
                glVertex2f( -target->getBoundingRadius(), halfHeight );
        }
        glEnd();

        glDisable( GL_BLEND );
        glDisable( GL_ALPHA_TEST );
        glDisable( GL_TEXTURE_2D );

        glDepthMask( GL_TRUE );
}

/**
 * this method assumes that the depth buffer is clear and the color buffer is
 * cleared to the right color, if any
 */

void DungeonScreen::paint() {

        class Player *player = Client::getPlayer();
        class Zone * zone = player->getContainerZone();
        
        if ( zone != cur_zone ) {

                cout << " :: Zone switch detected from zone "
                     << (cur_zone ? cur_zone->getName() : "(null)")
                     << " to zone "
                     << (zone ? zone->getName() : "(null)")
                     << endl;

                unloadZone( player, cur_zone );
                initZone( player, zone );
                //                ( (class World*) cur_zone->getContainer() )->removeZone( cur_zone );
                //                delete cur_zone;
        }

        GLint viewport[4];
        
        if ( cl_state.screen_size != 10 ) {
                
                glGetIntegerv( GL_VIEWPORT, viewport );
                
                glViewport( viewport[0] + viewport[2] * ( 10 - cl_state.screen_size ) / 20,
                            viewport[1] + viewport[3] * ( 10 - cl_state.screen_size ) / 20,
                            viewport[2] * cl_state.screen_size / 10,
                            viewport[3] * cl_state.screen_size / 10 );
        }
        
        g_state.show_cursor = false;//player->camera->type != Camera::FIRST_PERSON;
        

        class Camera * cam = player->camera;

        glMatrixMode( GL_PROJECTION );

        glPushMatrix();
        glLoadIdentity();
                
        my_gluPerspective( cl_state.fov, 1.3, 4, 5000 );

        glMatrixMode( GL_MODELVIEW );
                
        glLoadIdentity();
     
        if ( cam->type == Camera::FIRST_PERSON ) {
                glRotatef( player->getTilt() + 90, -1, 0, 0 );
                glRotatef( 90 - player->getAngle(), 0, 0, 1 );
                glTranslatef( -player->getPos()[0], -player->getPos()[1], 
                              -player->getPos()[2] + headbob - player->getEyeLevel() );
                
        }
        else {
                glTranslatef( 0, 0, -cam->zoom );
                glRotatef( cam->angles[0], 1, 0, 0 );
                glRotatef( 90 - cam->angles[2], 0, 0, 1 );
                glTranslatef( -player->getPos()[0], -player->getPos()[1],
                              -player->getPos()[2] - player->getEyeLevel() );
        }

        glDepthMask( 1 );
        glDepthFunc( GL_LESS );
        
        glDisable( GL_BLEND );
        glDisable( GL_DEPTH_TEST );
        glDisable( GL_ALPHA_TEST );

        glCullFace( GL_BACK );
        glEnable( GL_CULL_FACE );
        
        glEnable( GL_DEPTH_TEST );

        glShadeModel( GL_FLAT );

        //
        // draw the zone
        //
        //
        
        glColor3f( 0, 0.2, 0 );

        if ( cl_state.use_wireframe )
                glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
        else
                glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
        
        glColor3f( 1, 1, 1 );
        
        drawZone( player );

        if ( g_state.polygon_smooth_ok ) {
                glEnable( GL_POLYGON_SMOOTH );
                glEnable( GL_LINE_SMOOTH );
        }
   
        const Vector<class Entity *> & vec = zone->getContents();

        Vector< class Entity * >::const_iterator end = vec.end();

        for ( Vector< class Entity * >::const_iterator iter = vec.begin();
              iter != end; ++iter ) { 
                
                //
                // don't draw yourself!
                //
                
                if ( *iter == player )
                        continue;
                
                if ( (*iter)->die ) {
                        
                        const class Sprite * sprite = (*iter)->getSprite();
                        
                        if ( sprite && sprite->clientSprite ) {
                                delete sprite->clientSprite;
                                sprite->clientSprite = 0;
                        }
                        
                        continue;
                }

                class Entity * ent = *iter;
                
                glColor3f( 0.8, 0.8, 1 );
                glPushMatrix();
                {
                        glTranslatef( (*iter)->getPos()[0], (*iter)->getPos()[1], (*iter)->getPos()[2] );

                        const class Sprite * sprite = (*iter)->getSprite();

                        if ( sprite ) {
                                if ( ! sprite->clientSprite )
                                        sprite->clientSprite = new ClientSprite( sprite );
                                
                                showSprite( player, *iter );
                        }
                        
                        else {
                                glRotatef( ent->getAngle(), 0, 0, 1 );
                        
                                glPushMatrix();
                                
                                glRotatef( ent->getTilt()/8, 0, -1, 0 );
                        
                                const unsigned char * rgb = (*iter)->getIllumination();
                                glColor3ub( rgb[0], rgb[1], rgb[2] );
                                        
                                md2_model_t * my_model = ModelManager::getModel( ent->getModelName() );
                                class TextureObject * to  = TextureManager::getTexture( ent->getSkinName(), GL_RGBA );
                                        
                                class Character * ch = dynamic_cast< class Character *>( ent );

                                const float * trans = ent->getModelTrans();//my_model->getTrans( ent->getFrame() );
                                
                                glTranslatef( trans[0], trans[1], trans[2] );
                                        
                                //                                glTranslatef( 0, -trans[1], -trans[2] );

                                if ( my_model->isOk ) {
                                        drawModel( ent->getFrame(), *my_model, to, true );
                                                
                                        if ( ch ) {
                                                md2_model_t * my_weap_model = ModelManager::getModel( ch->getWeaponModelName() );
                                                class TextureObject * weap_to = TextureManager::getTexture( ch->getWeaponSkinName(), GL_RGBA );

                                                drawModel( ent->getFrame(), *my_weap_model, weap_to, true );
                                        }
                                }
                                        
                                glPopMatrix();
                                        
                                if ( cl_state.show_bounding || my_model->isOk == false ) {
                                        glDisable( GL_CULL_FACE );
                                        drawBounding( *iter, false, float(rgb[0])/255, float(rgb[1])/255, float(rgb[2])/255 );
                                        glEnable( GL_CULL_FACE );
                                }
                                
                        }
                        
                        ++g_state.num_ents;
                }
                glPopMatrix();


        }

        glPushMatrix();
        {

                const unsigned char * rgb = player->getIllumination();
                glColor4ub( rgb[0], rgb[1], rgb[2], 175 );
                
                if ( cam->type == Camera::FIRST_PERSON )  {
                        if ( draw_weapon ) {
                                glTranslatef( player->getPos()[0], 
                                              player->getPos()[1], 
                                              player->getPos()[2] + 24 );

                                glRotatef( player->getAngle(), 0, 0, 1 );
                                glTranslatef( 0, 0, 16 );
                                glRotatef( player->getTilt(), 0, -1, 0 );
                        
                                if ( player->getFrame() <= Character::FRAME_stand40 )
                                        glTranslatef( -8, 4, -8 );
                                else if ( player->getFrame() <= Character::FRAME_run6 )
                                        glTranslatef( -12, -8, -8 );
                                else if ( player->getFrame() <= Character::FRAME_attack8 )
                                        glTranslatef( -10, 4, -8 );
                                else if ( player->getFrame() <= Character::FRAME_jump6 )
                                        glTranslatef( 0, 0, -16 );
                        
                                glDisable( GL_DEPTH_TEST );
                                drawModel( player->getFrame(), *weapon_models[ cur_weapon ], weapon_textures[ cur_weapon ], true );
                                glEnable( GL_DEPTH_TEST );
                        }
                }
                else {
                        
                        md2_model_t * my_model = ModelManager::getModel( player->getModelName() );
                        md2_model_t * my_weap_model = ModelManager::getModel( player->getWeaponModelName() );
                        
                        class TextureObject * to  = TextureManager::getTexture( player->getSkinName(), GL_RGBA );
                        class TextureObject * weap_to  = TextureManager::getTexture( player->getWeaponSkinName(), GL_RGBA );

                        glTranslatef( player->getPos()[0],
                                      player->getPos()[1],
                                      player->getPos()[2] );

                        glRotatef( player->getAngle(), 0, 0, 1 );

                        glPushMatrix();
                        
                        glRotatef( player->getTilt()/8, 0, -1, 0 );
                        
                        const float * trans = my_model->getTrans( player->getFrame() );
                        
                        glTranslatef( trans[1]/4, 0, -trans[2] );

                        my_weap_model = weapon_models[ cur_weapon ];
                        weap_to  = weapon_textures[ cur_weapon ];
                        
                        drawModel( player->getFrame(), *my_weap_model, weap_to, true );
                        
                        drawModel( player->getFrame(), *my_model, to, true );

                        glPopMatrix();

                        if ( cl_state.show_bounding || my_model->isOk == false ) {
                                glDisable( GL_CULL_FACE );
                                drawBounding( player, false, float(rgb[0])/255, float(rgb[1])/255, float(rgb[2])/255 );
                                glEnable( GL_CULL_FACE );
                        }

                }
                //                        glTranslatef( 0, 0, -24 );
                //                        glCallList( _playerList );
        }
        glPopMatrix();

        if ( g_state.polygon_smooth_ok ) {
                glDisable( GL_POLYGON_SMOOTH );
                glDisable( GL_LINE_SMOOTH );
        }
        
        //
        // put the projection matrix back like we found it (ortho)
        //

        glMatrixMode( GL_PROJECTION );
        glPopMatrix();

        glDepthMask( 0 );
        glDisable( GL_DEPTH_TEST );

        glMatrixMode( GL_MODELVIEW );
                
        if ( draw_map_overlay ) {

                glLoadIdentity();
                
                glEnable( GL_BLEND );

                if ( g_state.polygon_smooth_ok ) {
                        glEnable( GL_POLYGON_SMOOTH );
                        glEnable( GL_LINE_SMOOTH );
                }

                glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
                
                glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

                glColor4f( 0.0, 0.0, 0.0, 0.6 );
                glBegin( GL_QUADS );
                {
                        glVertex2f(            16, 16 );
                        glVertex2f( _size[0] - 16, 16 );
                        glVertex2f( _size[0] - 16, _size[1] - 16 );
                        glVertex2f(            16, _size[1] - 16 );
                }
                glEnd();

                glTranslatef( _size[0]/2, _size[1]/2, 0 );
                float scalefact = float( map_zoom_factor ) / MAP_ZOOM_BASE;
                
                glScalef( scalefact, scalefact, 0 );
                glPushMatrix();
                glRotatef( 90 - player->getAngle(), 0, 0, 1 );
                glTranslatef( -player->getPos()[0], -player->getPos()[1], 0 );

                drawZoneMap( player );
                
                glDisable( GL_BLEND );

                for ( Vector< class Entity * >::const_iterator iter = vec.begin();
                      iter != end; ++iter ) { 
                        
                        //
                        // don't draw yourself!
                        //
                        
                        if ( *iter == player )
                                continue;
                        
                        glColor3f( 0.8, 0.8, 1 );
                        glPushMatrix();
                        {
                                glTranslatef( (*iter)->getPos()[0], (*iter)->getPos()[1], (*iter)->getPos()[2] );
                                if ( *iter == player->getTargetEntity() )
                                        drawDisc( (*iter)->getBoundingRadius(), 1.0, 1.0, 0.8 );
                                else
                                        drawDisc( (*iter)->getBoundingRadius(), 0.6, 0.6, 0.8 );
                        }
                        glPopMatrix();
                }

                glPopMatrix();
                glCallList( automap_playerdisc_list );
                
                if ( g_state.polygon_smooth_ok ) {
                        glDisable( GL_POLYGON_SMOOTH );
                        glDisable( GL_LINE_SMOOTH );
                }
                
        }
        
        glMatrixMode( GL_MODELVIEW );

        //
        // restore the viewport if needed
        //
        
        if ( cl_state.screen_size != 10 )
                glViewport( viewport[0], viewport[1], viewport[2], viewport[3] );
        
        if ( cam->msg ) { 

                Uint32 tick = SDL_GetTicks();

                if ( cam->msg_timeout < tick ) {
                        cam->setMessage( 0, 0 );
                }
                else {
                        glLoadIdentity();
                        glColor3f( 1, 1, 0.5 );
                        glTranslatef( ( _size[0] - _font->getStringWidth( cam->msg ) ) /2 , _size[1]/2, 0 );
                        _font->drawString( cam->msg, strlen( cam->msg ) );
                }
        }
}

static void playSound( class Sound * sound,  float dist ) {
        
        const class ClientSound * cs = ClientSound::addSound( sound->idnum, sound->volume, sound->loops );
                
        // null cs means we've run out of sound playing slots
        if ( cs == 0 )
                return;

        Mix_Chunk * chunk = 0;  
     
        switch ( sound->index ) {
        case Sound::SND_FIREBALL:
                chunk = fire_wav;
                break;
        case Sound::SND_EXPLOSION:
                chunk = explode_wav;
                break;
        case Sound::SND_LANDING:
                chunk = turn_wav;
                break;
        case Sound::SND_FOOTSTEP_SMALL:
        case Sound::SND_FOOTSTEP_MEDIUM:
        case Sound::SND_FOOTSTEP_BIG:
                chunk = footstep_wavs[ footstep_map[ cur_footstep ] ];
                        
                if ( ++cur_footstep >= num_mapped_footsteps ) {
                        cur_footstep = 0;
                }
                break;
        case Sound::SND_PAIN_FALL:
                chunk = pain_fall_wav;
                break;
        case Sound::SND_PAIN_MEDIUM:
                chunk = pain2_wav;
                break;
        case Sound::SND_SWORD_SLASH:
                chunk = slash_wav;
                break;
        case Sound::SND_HUMAN_MALE_JUMP:
                chunk = jump_wav;
                break;

        case Sound::SND_LIFT_STOP:
                chunk = lift_stop_wav;
                break;

        case Sound::SND_LIFT_MOVE:
                chunk = lift_move_wav;
                break;

        case Sound::SND_PICKUP_ITEM:
                chunk = pickup_wav;
                break; 
                
        case Sound::SND_NOPASS_MAGIC:
                chunk = nopass_magic_wav;
                break; 
        }
                
        if ( chunk == 0 ) {
                cout << " :: Unhandled sound type " << (int) sound->index << endl;
                sound->die = true;
                cs->terminate();
                return;
        }
                
        if ( cs->channel < 0 ) {
                cs->channel = Mix_PlayChannel( -1, chunk, cs->loops );
                //                        cout << " starting idnum=" << cs->idnum << " on channel=" << cs->channel << endl;
        }
                
        if ( cs->channel < 0 ) {
                return;
        }
                
        if ( ! Mix_Playing( cs->channel ) ) {
                cs->terminate();
                return;
        }
                
        //static const int MAX_DIST = 3100;
        //static const int MAX_DIST_SQ = MAX_DIST * MAX_DIST;

        float vol = float(cl_state.sfx_mix_vol) * cs->volume / 255;

        static const float MIN_VOLUME = 5;
        
        if ( cs->volume ) {
                dist /= 128;

                if ( dist < 1 )
                        dist = 1;
                else
                        vol /= dist;
        
                if ( vol < MIN_VOLUME )
                        vol = 0;
        }
        
        Mix_Volume( cs->channel, (int)vol);
        cs->ok = true;
                
        if ( sound->loops >= 0 )
                sound->die = true;     

}


static void playSounds( const class Entity * ent,
                        const class Character * listener ) {

        const float * lpos = listener->getPos();
        const float * pos = ent->getPos();
        float dist = -1.0;

        class Sound * sound = 0;

        const Vector< class Sound * > & sounds = ent->getSounds();
        for ( Vector< class Sound * >::const_iterator iter = sounds.begin();
              iter != sounds.end(); ++iter ) {

                sound = *iter;
                
                if ( sound->die ) {
                        const class ClientSound * cs = ClientSound::findSound( sound->idnum );
                        if ( cs && ! Mix_Playing( cs->channel ) ) {
                                cs->terminate();
                        }
                        continue;
                }
                
                if ( dist < 0 ) {
                        dist = ( ent == listener ) ? 0.0F : VMath::distance3d( lpos, pos );
                }

                playSound( sound, dist );
                
                break;
        }
}

static void updateSounds( const class Player * player, const float * pos ) {

        const class Zone * zone = player->getContainerZone();
        
        const Vector<class Entity *> & vec = zone->getContents();
        
        Vector< class Entity * >::const_iterator end = vec.end();

        ClientSound::tagSounds( false );
        
        for ( Vector< class Entity * >::const_iterator iter = vec.begin();
              iter != end; ++iter ) { 
                //                if ( *iter == player )
                //                        continue;
                playSounds( *iter, player );
        }
        
        const Array< class SectorSpecial *> & specs = zone->getSectSpecials();

        const class Sector * psect = player->getSector();

        for ( int i = 0; i < specs.length; ++i ) {
                class SectorSpecial * spec = specs[i];
                
                if ( spec->sound == 0 )
                        continue;
                
                if ( spec->sound->die ) {
                        const class ClientSound * cs = ClientSound::findSound( spec->sound->idnum );
                        if ( cs && ! Mix_Playing( cs->channel ) ) {
                                cs->terminate();
                        }
                        continue;
                }
                
                if ( spec->sect == 0 )
                        continue;
                
                float zs[2];

                spec->getWorldRange( &zs[0], &zs[1] );
                
                float lpos[3] = {
                        player->getPos()[0],
                        player->getPos()[1],
                        player->getPos()[2]
                };

                if ( lpos[2] < zs[0] )
                        lpos[2] -= zs[0];
                else if ( lpos[2] > zs[1] )
                        lpos[2] -= zs[1];
                else
                        lpos[2] = 0;

                lpos[2] += spec->sect->getCenterPoint()[2];
                         
                float dist = VMath::distance3d( lpos, spec->sect->getCenterPoint() );
                
                const Array<LineDef *> & zlines = zone->getLines();
                
                if ( psect && zone->sectorsCanSee( psect, spec->sect ) == false ) {
                        dist *= 2;

                        bool found = false;
                        for ( int i = 0; i < spec->sect->_myLines.length; ++i ) {
                                const class LineDef * line = zlines[ spec->sect->_myLines[i] ];
                                
                                if ( line->sides[0]->sector != spec->sect ) {
                                        if ( zone->sectorsCanSee( psect, line->sides[0]->sector ) ) {
                                                found = true;
                                                break;
                                        }
                                }
                                else if ( line->sides[1] ) {
                                        if ( zone->sectorsCanSee( psect, line->sides[1]->sector ) ) {
                                                found = true;
                                                break;
                                        }
                                }
                        }

                        if ( found == false )
                                dist *= 2;
                }

                playSound( spec->sound, dist );
        }

        if ( psect ) {
                
                const Array< class Sector * > & sectors = zone->getSectors();
                
                int min_dist = 99999;
                
                if ( psect->getFloorTex() && psect->getFloorTex()[9] == '_' ) {
                        min_dist = (int) ( player->getPos()[2] - psect->getFloorHeight() );
                }
                
                for ( int i = 0; i < psect->_visibleFloors.length; ++i ) {
                        const class Sector * sect = sectors[ psect->_visibleFloors[ i ] ];

                        // lava floor
                        if ( sect->getFloorTex() && sect->getFloorTex()[9] == '_' ) {
                                
                                float lpos[3] = {
                                        player->getPos()[0],
                                        player->getPos()[1],
                                        player->getPos()[2]
                                };  
                                
                                float centerPoint[3] = {
                                        sect->getCenterPoint()[0],
                                        sect->getCenterPoint()[1],
                                        sect->getFloorHeight()
                                };
                                
                                float dist = VMath::distance3d( lpos, centerPoint );
                                
                                if ( dist < min_dist )
                                        min_dist = (int) dist;
                        }
                }  

                if ( min_dist < 1000 ) {

                        min_dist = 1000 - min_dist;

                        // already playing, just adjust volume
                        if ( lava_channel >= 0 ) {
                                Mix_Volume( lava_channel, min_dist * cl_state.sfx_mix_vol / 1000 );
                        }
                        // start playing
                        else {
                                lava_channel = Mix_PlayChannel( -1, lava_wav, -1 );
                                
                                if ( lava_channel >= 0 )
                                        Mix_Volume( lava_channel, min_dist * cl_state.sfx_mix_vol / 1000 );
                        }
                }
                else {
                        // already playing, so stop
                        if ( lava_channel >= 0 ) {
                                Mix_HaltChannel( lava_channel );
                                lava_channel = -1;
                        }
                }
        }
                
        ClientSound::purgeSounds( false );
}

void DungeonScreen::updateWidget() {

        class Player *player = Client::getPlayer();


        //
        //

        Uint32 tick_begin = SDL_GetTicks();

        player->getContainerZone()->physicsUpdateContents( tick_begin );
        
        g_state.physics_time = SDL_GetTicks() - tick_begin;
        g_state.physics_fps = ( g_state.physics_time ? ( 1000 / g_state.physics_time ) : -1 );
        
        const float * pos = player->getPos();
        updateSounds( player, pos );
        
        Uint8 mouse_state = SDL_GetMouseState( 0, 0 );
        
        if ( pos[0] != last_pos[0] ||
             pos[1] != last_pos[1] ||
             pos[2] != last_pos[2] ) {
                
                //
                // first person view, bob head
                //
                /*
                  if ( player->camera->type == Camera::FIRST_PERSON  && 
                  ( pos[0] != last_pos[0] || pos[1] != last_pos[1] ) ) {

                  headbob += headbob_delta;
                  headbob_cur++;
                        
                  if ( headbob_cur >= headbob_max ) {
                  headbob_cur = -headbob_max;
                  headbob_delta = -headbob_delta;

                                //
                                // going back up, this is a footstep
                                //

                                if ( headbob_delta > 0 ) {
                                Mix_PlayChannel( -1, footstep_wavs[ footstep_map[ cur_footstep ] ], 0 );

                                if ( ++cur_footstep >= num_mapped_footsteps ) {
                                cur_footstep = 0;
                                }
                                }
                                }
                                }
                */
                //
                // third person view, follow the player with camera
                //

                last_pos[0] = pos[0];
                last_pos[1] = pos[1];
                last_pos[2] = pos[2];
        }
        
        class Camera * cam = player->camera;

        if ( cam->type == Camera::THIRD_PERSON_FOLLOW ) {
                
                float angle_diff = VMath::angleDiff360( player->getAngle(), cam->angles[2] );
                
                if ( fabs( angle_diff ) >= 2 )
                        angle_diff /= 2;

                cam->angles[2] = VMath::clampAngle360( cam->angles[2] + angle_diff );

                angle_diff = VMath::angleDiff360( -( player->getTilt() + 180 ) / 2, cam->angles[0] );
                
                if ( fabs( angle_diff ) >= 2 )
                        angle_diff /= 2;
              
                cam->angles[0] = VMath::clampAngle360( cam->angles[0] + angle_diff );

        }
                

        if ( ! getParent() ) {
                cout << " dungeon has no parent" << endl;
                return;
        }

        if ( ! player ) {
                cout << " dungeon has no player" << endl;
                return;
        }


        //
        // control of the character is being handled by a script
        //

        if ( player->hasControl == false )
                return;

        Uint8 * keystate = SDL_GetKeyState( 0 );
        SDLMod modstate = SDL_GetModState();
        
        if ( mouse_state & SDL_BUTTON( 1 ) ) {
                Client::getPlayer()->attack( 0 );
        }

        float self_prop[3] = { 0, 0, 0 };
        if ( mouse_state & SDL_BUTTON( 3 ) ) {
                self_prop[0] += 1;
        }
        
        if ( keystate[ SDLK_UP ] ) {
                if ( modstate & KMOD_CTRL ) {
                        if ( cam->type == Camera::FIRST_PERSON )
                                player->rotate( 0, 5 );
                        else
                                cam->angles[0] += 5;
                }
                else {
                        self_prop[0] += 1;
                }
        }
        if ( keystate[ SDLK_DOWN ] ) {
                if ( modstate & KMOD_CTRL ) {
                        if ( cam->type == Camera::FIRST_PERSON )
                                player->rotate( 0, -5 );
                        else
                                cam->angles[0] -= 5;
                }
                else {
                        self_prop[0] -= 1;
                }
        }

        if ( keystate[ SDLK_LEFT ] ) {
                if ( modstate & KMOD_CTRL ) {
                        cam->angles[2] = VMath::clampAngle360( cam->angles[2] - 5 );
                }
                else {
                        player->rotate( 5, 0 );
                }
        }
        if ( keystate[ SDLK_RIGHT ] ) {
                if ( modstate & KMOD_CTRL ) {
                        cam->angles[2] = VMath::clampAngle360( cam->angles[2] + 5 );
                }
                else {
                        player->rotate( -5, 0 );
                }
        }

        if ( keystate[ SDLK_a ] ) {
                self_prop[0] -= 1;
        }
        if ( keystate[ SDLK_s ] ) {
                self_prop[1] += 1;
        }
        if ( keystate[ SDLK_f ] && ( modstate & KMOD_CTRL ) == 0 ) {
                self_prop[1] -= 1;
        }

        if ( self_prop[0] || self_prop[1] ) {
                VMath::unitVector2d( self_prop );
        }
        
        int real_speed = cl_state.walk_speed;
        
        if ( modstate & KMOD_SHIFT ) {
                real_speed *= 3;
                real_speed /= 2;
        }

        player->setRelativeSelfPropulsion( self_prop[0] * real_speed , self_prop[1] * real_speed, 0 );

        if ( keystate[ SDLK_RIGHTBRACKET ] ) cam->zoom = max( 20, cam->zoom - 10 );
        if ( keystate[ SDLK_LEFTBRACKET ] ) cam->zoom = min( 4000, cam->zoom + 10 );

        if ( ! ( modstate & KMOD_CTRL )  ) {
                if ( keystate[ SDLK_w ] && cl_state.fov < 140 ) cl_state.fov += 5;
                if ( keystate[ SDLK_q ] && cl_state.fov > 5 ) cl_state.fov -= 5;
        }


        playSounds( player, player );
}

// class MouseButtonListener
bool DungeonScreen::mousePressed(  SDL_MouseButtonEvent * evt ) {
        return false;
}
bool DungeonScreen::mouseReleased( SDL_MouseButtonEvent * evt ) {
        return false;
}

// class MouseMotionListener {
bool DungeonScreen::mouseMoved(    SDL_MouseMotionEvent * evt ) {

        static const int center[2] = { 
                _size[0] / 2, 
                _size[1] / 2 
        };
        
        class Player *player = Client::getPlayer();
        
        if ( ( evt->x != center[0] || evt->y != center[1] )  ) {

                SDLMod modstate = SDL_GetModState();
                
                if ( player->hasControl ) {
                        float rot[2] = {
                                float( evt->xrel ) * cl_state.mouse_sensitivity * cl_state.fov / 90,
                                float( evt->yrel ) * cl_state.mouse_sensitivity * cl_state.fov / 90
                        };
                        
                        if ( cl_state.reverse_mouse_y )
                                rot[1] = -rot[1];

                        class Camera * cam = player->camera;

                        // change the camera
                        if ( cam->type == Camera::THIRD_PERSON && ( modstate & KMOD_CTRL ) ) {
                                // zoom
                                if ( modstate & KMOD_SHIFT ) {
                                        cam->zoom = max( 20, min( cam->zoom + (int) rot[1], 4000 ) );
                                }
                                // rotate
                                else {
                                        cam->angles[0] -= rot[1];
                                        cam->angles[2] -= rot[0];
                                }
                        }
                        
                        else {
                        
                                player->rotate( - rot[0], rot[1] );
                        }

                }
                
                if ( g_state.grab_input )
                        SDL_WarpMouse( center[0], center[1] );
                
        }

        playSounds( player, player );

        return false;
}
bool DungeonScreen::mouseEntered(  SDL_MouseMotionEvent * evt ) {
        return false;
}
bool DungeonScreen::mouseExited(   SDL_MouseMotionEvent * evt ) {
        return false;
}

bool DungeonScreen::keyReleased( SDL_KeyboardEvent * evt ) {
        if ( GenericScreen::keyReleased( evt ) )
                return true;
        
        return false;
        
}

bool DungeonScreen::keyPressed(   SDL_KeyboardEvent * evt, int x, int y ) {
        if ( GenericScreen::keyPressed( evt, x, y ) )
                return true;
        
        class Player *player = Client::getPlayer();
        SDLMod modstate = SDL_GetModState();

        //
        // first handle the controls which may be used at all times
        //
        
        switch ( evt->keysym.sym ) {

        case SDLK_TAB:
                draw_map_overlay = ! draw_map_overlay;
                break;

        case SDLK_EQUALS:
                if ( draw_map_overlay ) {
                        if ( map_zoom_factor < MAP_ZOOM_BASE )
                                map_zoom_factor <<= 1;
                }
                else {
                        if ( cl_state.screen_size < 10 )
                                ++cl_state.screen_size;
                }

                break;

        case SDLK_MINUS:
                if ( draw_map_overlay ) {
                        if (  map_zoom_factor > 1 )
                                map_zoom_factor >>= 1;
                }
                else {
                        if ( cl_state.screen_size > 1 )
                                --cl_state.screen_size;
                }
                break;
                        
        case SDLK_f:
                if ( modstate & KMOD_CTRL ) {
                        cl_state.use_wireframe = ! cl_state.use_wireframe;
                }
                break;

        case SDLK_b:
                if ( modstate & KMOD_CTRL ) {
                        cl_state.show_bounding = ! cl_state.show_bounding;
                }
                break;
        case SDLK_w:
                if ( modstate & KMOD_CTRL ) {
                        draw_weapon = ! draw_weapon;
                }
                break;
        case SDLK_d:
                if ( modstate & KMOD_CTRL ) {
                        class Zone * zone = player->getContainerZone();
                        
                        zone->setUseDynamicLights( ! zone->getUseDynamicLights() );
                }
                break;

        case SDLK_t:
                if ( modstate & KMOD_CTRL ) {
                        cl_state.use_textures = !cl_state.use_textures;
                }
                break;

        case SDLK_l:
                if ( modstate & KMOD_CTRL ) {
                        cl_state.use_lightmaps = !cl_state.use_lightmaps;
                }
                break;

        case SDLK_v:
                if ( modstate & KMOD_CTRL ) {
                        cl_state.use_pvs = !cl_state.use_pvs;
                }
                break;
                
        case SDLK_s:
                 if ( modstate & KMOD_CTRL ) {
                         g_state.show_stats = !g_state.show_stats;
                 }
                break;
        case SDLK_i:
                if ( modstate & KMOD_CTRL ) {
                        draw_invisible_sectors = !draw_invisible_sectors;
                }
                break;

        default:
                break;
        }

        //
        // see if the player is in control of his character (e.g. not cutscene or scripted)
        //
        
        if ( player->hasControl == false ) {
                playSounds( player, player );
                return false;
        }
        
        //
        // handle controls such as movement and camera control
        //
        
        switch ( evt->keysym.sym ) {
        case SDLK_0:
                cur_weapon = 0;
                break;
        case SDLK_1:
                cur_weapon = 1;
                break;
        case SDLK_2:
                cur_weapon = 2;
                break;
        case SDLK_3:
                cur_weapon = 3;
                break;
        case SDLK_4:
                cur_weapon = 4;
                break;
        case SDLK_5:
                cur_weapon = 5;
                break;
        case SDLK_6:
                cur_weapon = 6;
                break;
        case SDLK_7:
                cur_weapon = 7;
                break;
        case SDLK_8:
                cur_weapon = 8;
                break;
        case SDLK_9:
                cur_weapon = 9;
                break;
        case SDLK_p:
                cl_state.walk_speed += 2;
                break;
        case SDLK_o:
                cl_state.walk_speed -= 2;
                break;

                //
                // toggle camera
                //

        case SDLK_F9:
                switch ( player->camera->type ) {
                case Camera::FIRST_PERSON:
                        player->camera->type = Camera::THIRD_PERSON_FOLLOW;
                        break;
                case Camera::THIRD_PERSON_FOLLOW:
                        player->camera->type = Camera::THIRD_PERSON;
                        player->camera->angles[2] = player->getAngle();
                        player->camera->angles[0] = -60;
                        break;
                case Camera::THIRD_PERSON:
                        player->camera->type = Camera::FIRST_PERSON;
                        //                        player->camera->angles[0] = -90;
                        g_state.show_cursor = false;
                        break;
                }

                break;


        case SDLK_y:
                if ( modstate & KMOD_CTRL ) {
                        player->toggleFlag( Entity::FLYING );
                }
                break;
        case SDLK_UP:

                if ( modstate )
                        break;

                //                player->setRelativeSelfPropulsion( 10, 0, 0 );
                break;

        case SDLK_DOWN:

                if ( modstate )
                        break;

                //                player->setRelativeSelfPropulsion( -10, 0, 0 );
                break;

        case SDLK_SPACE:
        case SDLK_BACKSPACE:
                player->jump();
                //                player->addRelativeSelfPropulsion( 0, 0, 48 );
                break;

        default:
                break;
        }

        playSounds( player, player );

        return false;
}

