#include <io.h>
#include "dk_shared.h"
#include "qfiles.h"

#include "dk_misc.h"
#include "dk_menu.h"
#include "dk_menup.h"
#include "dk_model.h"
#include "winquake.h"
#include "client.h"

//we keep track of the mouse position.
static int dkm_mouse_x = 320;
static int dkm_mouse_y = 240;

//true if we are drawing the menu up on the screen.
static bool dkm_in_menu = false;

//returns 1 if we are in the menu, 0 otherwise.
int DKM_InMenu() {
    return (dkm_in_menu == true) ? 1 : 0;
}

static int cursor_offset_x = 6;
static int cursor_offset_y = 8;

static interface_bmp_desc bitmaps[DKB_NUM] = {
    {"/pics/interface/cursor_01.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_02.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_03.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_04.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_05.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_06.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_07.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_08.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_09.bmp", RESOURCE_INTERFACE_TRANS},
    {"/pics/interface/cursor_10.bmp", RESOURCE_INTERFACE_TRANS},

    {"/pics/interface/back00.bmp"},
    {"/pics/interface/back01.bmp"},
    {"/pics/interface/back02.bmp"},
    {"/pics/interface/back10.bmp"},
    {"/pics/interface/back11.bmp"},
    {"/pics/interface/back12.bmp"},
};

//the interface palette.
static byte interface_palette[768];
//true if we have loaded the interface palette
bool palette_loaded = false;

//the models we use.
model_t *button_model = NULL;
model_t *box_model = NULL;

//the main menu object.
static CMenuMain menu;

//the area of the screen covered by the buttons can be represented by a parallelogram.
//the left and right of the parallelogram.
static int button_left = 506;
static int button_right = 593;
//the top and bottom scanlines of the left vertical parallelogram edge.
static int button_left_top = 76;
static int button_left_bottom = 436;
//the top and bottom scanlines of the right vertical parallelogram edge.
static int button_right_top = 55;
static int button_right_bottom = 462;

//we keep track of elapsed time from frame to frame.
static int last_frame_time = 0;
//when we first enter the menu, we need to initialize last_frame_time
static bool last_frame_time_init = false;

//given a x coordinate, computes the top and bottom vertical scan that lies over the buttons.
//returns true if the x coordinate is within the parallelogram, false otherwise.
bool DKM_ParallelogramColumn(int x, int &top, int &bottom) {
    //check if the x is on the buttons.
    if (x < button_left) return false;
    if (x > button_right) return false;

    //get the slope of the top line.  static so it only gets computed once.
    static float slope_top = float(button_right_top - button_left_top) / float(button_right - button_left);
    //get the intercept of the top line. 
    static float int_top = button_left_top - slope_top * button_left;

    //get slope and intercept of the bottom line.
    static float slope_bottom = float(button_right_bottom - button_left_bottom) / float(button_right - button_left);
    static float int_bottom = button_left_bottom - slope_bottom * button_left;

    //get the y values for our x coordinate.
    top = int(slope_top * x + int_top);
    bottom = int(slope_bottom * x + int_bottom);

    return true;
}

//given a y coordinate and the top and bottom of a line, returns the button that the y value falls on.
dk_main_button DKM_ButtonNum(int x, int y) {
    //get the top and bottom of the vertical scan at the given x.
    int top, bottom;
    if (DKM_ParallelogramColumn(x, top, bottom) == false) {
        //we didnt hit a button.
        return DKMB_NONE;
    }

    //just divide the top to bottom span into as many pieces as we have buttons
    dk_main_button button = dk_main_button(int(float(y - top) / float(bottom - top) * DKMB_NUM_BUTTONS));

    //do some error checking.
    if (button < 0) return (dk_main_button)0;
    if (button >= DKMB_NUM_BUTTONS) return (dk_main_button)(DKMB_NUM_BUTTONS - 1);

    return button;
}

void DKM_Enter() {
    //check if we have loaded our palette.
    if (palette_loaded == false) {
        //load the palette.
        for (int i = 0; i < 256; i++) {
            interface_palette[i * 3 + 0] = i;
            interface_palette[i * 3 + 1] = i;
            interface_palette[i * 3 + 2] = i;
        }
    }

    //we are entering the menu.
    dkm_in_menu = true;

    dkm_mouse_x = 320;
    dkm_mouse_y = 240;

    //cache all of our bitmaps.
    for (int i = 0; i < DKB_NUM; i++) {
        re.RegisterPic(bitmaps[i].name, bitmaps[i].flags | RESOURCE_INTERFACE);
    }

    //register the button model.
    button_model = re.RegisterModel("models/interface/testbutton/testbutton.md2", RESOURCE_INTERFACE);
    box_model = re.RegisterModel("models/interface/unitbox/unitbox.md2", RESOURCE_INTERFACE);

    //move the windows cursor to the middle of our window.
    //get the rectangle of the window.
    POINT tl, br;
    tl.x = tl.y = 0;
    br.x = 640;
    br.y = 480;
    ClientToScreen(cl_hwnd, &tl);
    ClientToScreen(cl_hwnd, &br);

    //set the cursor position.  add 320,240 to the position of our window.
    SetCursorPos(tl.x + 320, tl.y + 240);

    //make the mouse confined to our window.
    RECT window_rect;
    window_rect.left = tl.x;
    window_rect.top = tl.y;
    window_rect.right = br.x;
    window_rect.bottom = br.y;
    ClipCursor(&window_rect);

    //tell the main menu that we are entering.
    menu.Enter();
}

void DKM_Leave() {
    //make sure we haven't already left the menu
    if (dkm_in_menu == false) return;

    //purge the interface resources.
    re.PurgeResources(RESOURCE_INTERFACE);

    dkm_in_menu = false;

    //free the mouse.
    ClipCursor(NULL);

    //tell the main menu we are leaving.
    menu.Exit();

    //when we enter the menu again, reset our time tracking vars.
    last_frame_time_init = false;
}

//called every frame with the amount the mouse moved since last frame.
void DKM_MouseMove(int mx, int my) {
    //add the displacements to our position.
    dkm_mouse_x += mx;
    dkm_mouse_y += my;

    //keep the cursor on the screen.
    bound_min(dkm_mouse_x, 0);
    bound_max(dkm_mouse_x, 639);
    bound_min(dkm_mouse_y, 0);
    bound_max(dkm_mouse_y, 479);

    //do mouse movement processing.
    DKM_NewMousePos();
}

void DKM_MousePos(int x, int y) {
    dkm_mouse_x = x;
    dkm_mouse_y = y;

    //keep the cursor on the screen.
    bound_min(dkm_mouse_x, 0);
    bound_max(dkm_mouse_x, 639);
    bound_min(dkm_mouse_y, 0);
    bound_max(dkm_mouse_y, 479);

    //do mouse movement processing.
    DKM_NewMousePos();
}

void DKM_DrawString(int x, int y, const char *str) {
    if (str == NULL) return;

    for (; *str != '\0'; str++) {
        //draw the character.
        re.DrawChar(x, y, str[0]);
        
        //move our x over.
        x += 8;
    }
}

void DKM_Draw() {
    //get the elapsed time since last frame.
    int elapsed_time;

    if (last_frame_time_init == false) {
        //haven't initialized our previous time.
        last_frame_time_init = true;

        last_frame_time = GetTickCount();
        elapsed_time = 0;
    }
    else {
        //get the current time.
        int cur_time = GetTickCount();

        //subtract the current time from the time last frame.
        elapsed_time = cur_time - last_frame_time;

        bound_max(elapsed_time, 50);

        //remember the current time for next time.
        last_frame_time = cur_time;
    }
  
    //animate the menu.
    menu.Animate(elapsed_time);

    //set the palette.
    re.CinematicSetPalette(&interface_palette[0]);

    menu.Draw();

    menu.ShuttingDown(elapsed_time);
}

void DKM_Key(int key, qboolean down) {
    //check if this is a mouse message.
    if (key == K_MOUSE1) {
        if (down != false) {
            DKM_MouseDown();
        }
        else {
            DKM_MouseUp();
        }
        return;
    }

    if (down != false) {
        menu.Keydown(key);
    }
}

void DKM_NewMousePos() {
    menu.MousePos(dkm_mouse_x, dkm_mouse_y);
}

void DKM_MouseDown() {
    menu.MouseDown(dkm_mouse_x, dkm_mouse_y);
}

void DKM_MouseUp() {
    menu.MouseUp(dkm_mouse_x, dkm_mouse_y);
}

///////////////////////////////////////////////////////////////////////////////////
//
//  The base menu class
//
///////////////////////////////////////////////////////////////////////////////////

CMenu::CMenu() {
}

CMenu::~CMenu() {
}

//called when the menu is first entered.
void CMenu::Enter() {

}

//called when the menu is exited.
void CMenu::Exit() {

}

void CMenu::Animate(int elapsed_time) {

}

//draws the menu
void CMenu::DrawForeground() {
    DKM_DrawString(100, 200, "Daikatana is DAMN COOL ! !");
}

void CMenu::DrawBackground() {

}

void CMenu::PlaceEntities() {
    return;
}

//called when the mouse is moved
bool CMenu::MousePos(int x, int y) {
    return false;
}

bool CMenu::MouseDown(int x, int y) {
    return false;
}

bool CMenu::MouseUp(int x, int y) {
    return false;
}

//accepts keyboard input.  
//returns true if the key was handled.
bool CMenu::Keydown(int key) {
    return false;
}

bool CMenu::Keyup(int key) {
    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Main Menu class.
//
///////////////////////////////////////////////////////////////////////////////////

CMenuMain::CMenuMain() {
    //initialize our state.
    selection = DKMB_NONE;

    //initialize our button pointers.
    sub_menu[0] = new CMenuSubNewgame();
    sub_menu[1] = new CMenuSubMultiplayer();
    sub_menu[2] = new CMenuSubLoadgame();
    sub_menu[3] = new CMenuSubSavegame();
    sub_menu[4] = new CMenuSubSound();
    sub_menu[5] = new CMenuSubVideo();
    sub_menu[6] = new CMenuSubMouse();
    sub_menu[7] = new CMenuSubKeyboard();
    sub_menu[8] = new CMenuSubJoystick();
    sub_menu[9] = new CMenuSubGeneral();
    sub_menu[10] = new CMenuSubAdvanced();
    sub_menu[11] = new CMenuSubDemos();
    sub_menu[12] = new CMenuSubHelp();
    sub_menu[13] = new CMenuSubQuit();

    //init banner vars.
    banner_animation_time = 0;
    banner_status = HS_CLOSED;
    banner_text = DKMB_NONE;

    //init our shutdown vars.
    shutting_down = false;
    shutdown_time = 0;
    console_shutdown_string[0] = '\0';
}

CMenuMain::~CMenuMain() {
    for (int i = 0; i < DKMB_NUM_BUTTONS; i++) {
        delete sub_menu[i];
    }
}

void CMenuMain::Enter() {
    Select(DKMB_NONE);

    S_StartLocalSound("menus/Enter Menu_001.wav");

    shutting_down = false;
    console_shutdown_string[0] = '\0';
}

void CMenuMain::Exit() {
    Select(DKMB_NONE);
}

char *button_skins[DKMB_NUM_BUTTONS] = {
    "/models/interface/testbutton/new_game.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/mouse.bmp",
    "/models/interface/testbutton/keyboard.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/demos.bmp",
    "/models/interface/testbutton/blank.bmp",
    "/models/interface/testbutton/quit.bmp"
};

void CMenuMain::PlaceButtons() {
    //positioning data for the buttons.
    static vec3_t button_top_origin = {35, -2.8, 8.35};
    static vec3_t button_top_direction = {0, 210, 0};
    static float button_height = 1.165f;
    static float button_scale = 1.09f;

    //the current z value we will draw the model at.
    float button_z = button_top_origin[2];

    for (int i = 0; i < DKMB_NUM_BUTTONS; i++) {
        //get an entity structure we can fill in.
        entity_t *button = NewEntity();

        //assign the model pointer.
        button->model = button_model;

        //if the button is disabled put the blank skin on it.
        button->skin = re.RegisterSkin(button_skins[i], RESOURCE_INTERFACE);

        //make the button fullbright.
        button->flags = RF_FULLBRIGHT;

        //set the origin.
        button->origin[0] = button_top_origin[0];
        button->origin[1] = button_top_origin[1];
        button->origin[2] = button_z;

        //set the angles.
        button->angles[0] = button_top_direction[0];
        button->angles[1] = button_top_direction[1];
        button->angles[2] = button_top_direction[2];

        //set the scale
        button->render_scale[0] = button->render_scale[1] = button->render_scale[2] = button_scale;

        //set the frame
        button->frame = sub_menu[i]->Frame();

        //move the z down a little.
        button_z -= button_height;
    }
}

void CMenuMain::PlaceBanner() {
    //positioning info for the banner.
    static vec3_t closed_pivot = {2000, 100, 330};
    static vec3_t scale = {15, 500, 100};
    float slide_forward_disp = scale[2] - scale[0];
    float rotation_radius = slide_forward_disp * 0.5f;
    vec3_t down = {0, 0, -1 * rotation_radius};
    vec3_t forward = {-1 * rotation_radius, 0, 0};
    vec3_t rotation_pivot_pos = {closed_pivot[0] - slide_forward_disp, closed_pivot[1], closed_pivot[2]};

    //get an entity structure to fill in.
    entity_t *banner = NewEntity();

    //set the model pointer.
    banner->model = box_model;
    switch (banner_text) {
        case DKMB_NEWGAME: banner->skin = re.RegisterSkin("/models/interface/unitbox/bannewgame.bmp", RESOURCE_INTERFACE); break;
        case DKMB_MOUSE: banner->skin = re.RegisterSkin("/models/interface/unitbox/banmouse.bmp", RESOURCE_INTERFACE); break;
        case DKMB_KEYBOARD: banner->skin = re.RegisterSkin("/models/interface/unitbox/bankeyboard.bmp", RESOURCE_INTERFACE); break;
        case DKMB_DEMOS: banner->skin = re.RegisterSkin("/models/interface/unitbox/bandemos.bmp", RESOURCE_INTERFACE); break;
        case DKMB_QUIT: banner->skin = re.RegisterSkin("/models/interface/unitbox/banquit.bmp", RESOURCE_INTERFACE); break;
        default: banner->skin = re.RegisterSkin("/models/interface/unitbox/bandemos.bmp", RESOURCE_INTERFACE); break;
    }

    //make it fullbright.
    banner->flags = RF_FULLBRIGHT;

    //the angle that the banner is pitched at
    static float pitch = -90;
    //the center of the banner rotation.
    vec3_t pivot_pos = {rotation_pivot_pos[0], rotation_pivot_pos[1], rotation_pivot_pos[2]};

    //check our state
    if (banner_status == HS_CLOSED) {
        pitch = 90;
        pivot_pos[0] = closed_pivot[0];
        pivot_pos[1] = closed_pivot[1];
        pivot_pos[2] = closed_pivot[2];
    }
    else if (banner_status == HS_SLIDE_FORWARD) {
        pitch = 90;
        float percent = float(banner_animation_time) / ANIMATION_BANNER_TURN;
        pivot_pos[0] = (rotation_pivot_pos[0] - closed_pivot[0]) * percent + closed_pivot[0];
        pivot_pos[1] = (rotation_pivot_pos[1] - closed_pivot[1]) * percent + closed_pivot[1];
        pivot_pos[2] = (rotation_pivot_pos[2] - closed_pivot[2]) * percent + closed_pivot[2];
    }
    else if (banner_status == HS_OPENING) {
        float percent = float(banner_animation_time) / ANIMATION_BANNER_TURN;
        pitch = 90.0f - percent * 90.0f;
    }
    else if (banner_status == HS_OPEN) {
        pitch = 0;
    }
    else if (banner_status == HS_CLOSING) {
        float percent = float(banner_animation_time) / ANIMATION_BANNER_TURN;
        pitch = -90.0f * percent;
    }
    else if (banner_status == HS_OPEN_WIGGLE) {
        float percent = banner_animation_time / float(ANIMATION_BANNER_WIGGLE);
        float time = 1 + banner_animation_time * 0.001;
        float frequency = 4 + 2 * pow(time, 3);;
        float max_amplitude = 50;
        float dampener = pow(2.0f, percent * -10);
        pitch = -sin(banner_animation_time * 0.001 * M_PI * 2 * frequency) * max_amplitude * dampener;
    }
    else {
    }

    //get sin and cos of our pitch.
    float sp = float(sin(pitch * M_PI / 180.0f));
    float cp = float(cos(pitch * M_PI / 180.0f));

    //set the origin.
    banner->origin[0] = pivot_pos[0] + down[0] * cp + forward[0] * sp;
    banner->origin[1] = pivot_pos[1] + down[1] * cp + forward[1] * sp;
    banner->origin[2] = pivot_pos[2] + down[2] * cp + forward[2] * sp;

    //set the angles.
    banner->angles[0] = pitch;
    banner->angles[1] = 0;
    banner->angles[2] = 0;

    //set the scale.
    banner->render_scale[0] = scale[0];
    banner->render_scale[1] = scale[1];
    banner->render_scale[2] = scale[2];

    //set the frame
    banner->frame = 0;
}

void CMenuMain::ShutdownString(const char *format, ...) {
    //get our parameter list.
    va_list args;
    va_start(args, format);

    //print to our buffer.
    _vsnprintf(console_shutdown_string, 2048, format, args);
}

void CMenuMain::Shutdown() {
    shutting_down = true;
    shutdown_time = 0;

    //play our exit sound.
    S_StartLocalSound("menus/Exit Menu_001.wav");

    //exit all submenus
    Enter(DKMB_NONE);
}

entity_t *NewEntity() {
    return menu.NewEntity();
}

entity_t *CMenuMain::NewEntity() {
    //increment the number of entities.
    num_entities++;

    //return the next entity struct to be used.
    return &entities[num_entities - 1];
}

void CMenuMain::RenderView() {
    if (button_model == NULL) return;

    if (DKM_InMenu() == false) {
        return;
    }

    refdef_t refdef;
    memset(&refdef, 0, sizeof(refdef));

//    refdef.vieworg[2] = 100;
	refdef.x = scr_vrect.x;
	refdef.y = scr_vrect.y;
	refdef.width = viddef.width;
	refdef.height = viddef.height;
    refdef.fov_x = 40.0f;
	refdef.fov_y = CalcFov(refdef.fov_x, refdef.width, refdef.height);
	refdef.time = cl.time*0.001;
	refdef.areabits = cl.frame.areabits;
    refdef.rdflags = RDF_NOWORLDMODEL;

    //initialize our entity list.
    num_entities = 0;

    //initialize the entities we are going to draw.
    menu.PlaceEntities();

    //add the entities.
    refdef.num_entities = num_entities;
    refdef.entities = &entities[0];

    //render the frame.
    re.RenderFrame (&refdef);
}

void CMenuMain::Draw() {
    //draw the background images.
    re.DrawPic(0, 0, bitmaps[DKB_BACK00].name);
    re.DrawPic(256, 0, bitmaps[DKB_BACK01].name);
    re.DrawPic(512, 0, bitmaps[DKB_BACK02].name);
    re.DrawPic(0, 256, bitmaps[DKB_BACK10].name);
    re.DrawPic(256, 256, bitmaps[DKB_BACK11].name);
    re.DrawPic(512, 256, bitmaps[DKB_BACK12].name);

    //draw the background of the selected menu.
    if (selection != DKMB_NONE) {
        sub_menu[selection]->DrawBackground();
    }

    //draw the models.
    RenderView();

    //draw the foreground of the selected menu.
    if (selection != DKMB_NONE) {
        sub_menu[selection]->DrawForeground();
    }

    //get the cursor pic we will use this frame.
    int cursor = int(float(last_frame_time % CURSOR_TOTAL_TIME) / CURSOR_TOTAL_TIME * CURSOR_NUM_FRAMES);

    //draw the cursor.
    re.DrawPic(dkm_mouse_x - cursor_offset_x, dkm_mouse_y - cursor_offset_y, bitmaps[dk_interface_bmp(DKB_CURSOR_00 + cursor)].name);
}

void CMenuMain::PlaceEntities() {
    //put the buttons in the correct position.
    PlaceButtons();

    //put our banner in the correct position.
    PlaceBanner();

    //tell the selected sub-menu to place it's entities.
    if (selection != DKMB_NONE) {
        sub_menu[selection]->PlaceEntities();
    }
}

bool CMenuMain::MousePos(int x, int y) {
    //check the sub-menu
    if (selection != DKMB_NONE) {
        sub_menu[selection]->MousePos(x, y);
        //even if the sub-menu handled the move, we
        //still process the new mouse position.
    }

    //get the current time.
    int cur_time = GetTickCount();

    //the button that the mouse is over.
    dk_main_button button_hit = DKM_ButtonNum(x, y);

    //if the button dest status is BS_START, set it to BS_HILIGHT.
    if (button_hit != DKMB_NONE && sub_menu[button_hit]->dest_status == BS_START && sub_menu[button_hit]->enabled == true) {
        //change the destination status.
        sub_menu[button_hit]->dest_status = BS_HILIGHT;

        //get the current status of the button.
        dk_button_status status = sub_menu[button_hit]->status;

        //check what state we are in now.
        if (status == BS_START || status == BS_HILIGHT_BACKWARD) {
            //change to hilight forward status.
            sub_menu[button_hit]->status = BS_HILIGHT_FORWARD;
            //remember the time we entered the state.
            sub_menu[button_hit]->time = 0;
        }
    }

    //move other buttons back to the start status if we have to.
    for (int i = 0; i < DKMB_NUM_BUTTONS; i++) {
        //make sure this isnt the one we are on now.
        if (i == button_hit) continue;

        //check the dest status of this button.
        if (sub_menu[i]->dest_status == BS_HILIGHT) {
            //make the dest status BS_START
            sub_menu[i]->dest_status = BS_START;

            //check our current status.
            dk_button_status status = sub_menu[i]->status;
            if (status == BS_HILIGHT || status == BS_HILIGHT_FORWARD) {
                //change to the BS_HILIGHT_BACKWARD status.
                sub_menu[i]->status = BS_HILIGHT_BACKWARD;
                //remember the time we started.
                sub_menu[i]->time = 0;
            }
        }
    }

    return button_hit != DKMB_NONE;
}

bool CMenuMain::Keyup(int key) {
    return false;
}

bool CMenuMain::Keydown(int key) {
    //check the sub-menu
    if (selection != DKMB_NONE) {
        if (sub_menu[selection]->Keydown(key) == true) {
            //the sub-menu handled the key.
            return true;
        }
    }

    dk_main_button selected_menu = DKMB_NONE;

    switch (key) {
        case K_ESCAPE:
            //shut down the menu.
//            M_PopMenu();
//            DKM_Leave();

            Shutdown();
            return true;
        case 'q':
            selected_menu = DKMB_QUIT;
            break;
        case 'k':
            selected_menu = DKMB_KEYBOARD;
            break;
        case 'm':
            selected_menu = DKMB_MOUSE;
            break;
        case 'n':
            selected_menu = DKMB_NEWGAME;
            break;
        default:
            return false;
    }

    //make sure we have a menu to select.
    if (selected_menu == DKMB_NONE) return true;

    //make sure the selected menu button isnt animating.
    if (sub_menu[selected_menu]->status == BS_SELECT_BACKWARD) return true;

    //enter the menu
    Enter(selected_menu);

    return true;
}

bool CMenuMain::MouseUp(int x, int y) {
    if (selection != DKMB_NONE) {
        if (sub_menu[selection]->MouseUp(x, y) == true) {
            //the sub-menu handled the event.
            return true;
        }
    }

    return false;
}

bool CMenuMain::MouseDown(int x, int y) {
    //check the sub-menu
    if (selection != DKMB_NONE) {
        if (sub_menu[selection]->MouseDown(x, y) == true) {
            //the sub-menu handled the click.
            return true;
        }
    }

    //get the button that the mouse on top of.
    dk_main_button button_hit = DKM_ButtonNum(x, y);
    if (button_hit == DKMB_NONE) {
        return false;
    }
    //make sure the submenu is working.
    if (sub_menu[button_hit]->enabled == false) {
        return false;
    }
    //make sure the button is not in an animation.
    if (sub_menu[button_hit]->status == BS_SELECT_BACKWARD) {
        return false;
    }

    //select our new button.
    Select(button_hit);

    //if any other buttons are selected, we need to send them back to the selected status
    for (int i = 0; i < DKMB_NUM_BUTTONS; i++) {
        if (i == button_hit) continue;

        //unselect this button.
        CheckUnselect((dk_main_button)i);
    }

    //call DKM_NewMousePos, so hilight detection can occur for any buttons we just deselected.
    MousePos(x, y);

    return true;
}

void CMenuMain::CheckUnselect(dk_main_button button) {
    //check the dest status.
    if (sub_menu[button]->dest_status == BS_SELECTED) {
        //send it back to the hilight status.
        sub_menu[button]->dest_status = BS_HILIGHT;

        //check our current status.
        dk_button_status status = sub_menu[button]->status;
        if (status == BS_SELECTED || status == BS_SELECT_FORWARD) {
            //go to select_backward
            sub_menu[button]->status = BS_SELECT_BACKWARD;
            //remember the time.
            sub_menu[button]->time = 0;

            S_StartLocalSound("menus/600ms rotate decend_001.wav");
        }
        //else, we are in hilight forward status, dont do anything.
    }
}

void CMenuMain::Enter(dk_main_button button) {
    Select(button);
}

//called when a new button is selected
void CMenuMain::Select(dk_main_button button) {
    //check if the selection is changing.
    if (button == selection) return;

    //leave our old state.
    if (selection != DKMB_NONE) {
        //tell the menu we are exiting it.
        sub_menu[selection]->Exit();
        //put the button out of selected status.
        CheckUnselect(selection);
    }

    //set our state
    selection = button;

    //enter the new menu
    if (selection != DKMB_NONE) {
        sub_menu[selection]->Enter();

        //set this button so that it starts flipping around.
        sub_menu[selection]->dest_status = BS_SELECTED;

        dk_button_status status = sub_menu[selection]->status;
        //change the status of the button.
        if (status == BS_START || status == BS_HILIGHT_BACKWARD || status == BS_HILIGHT_FORWARD || 
            status == BS_HILIGHT || status == BS_SELECT_BACKWARD) 
        {
            //move it towards the selected state.
            sub_menu[selection]->status = BS_SELECT_FORWARD;

            //save the time.
            sub_menu[selection]->time = 0;

            S_StartLocalSound("menus/600ms rotate acsend_001.wav");
        }
    }
}

void CMenuMain::ShuttingDown(int elapsed_time) {
    //check if we are in our shutdown sequence.
    if (menu.shutting_down == true) {
        //increment the time.
        shutdown_time += elapsed_time;

        //check if we are done with the shutdown sequence.
        if (shutdown_time >= SHUTDOWN_SEQUENCE_TIME) {
            //send our shutdown string to the console.
            Cbuf_AddText(console_shutdown_string);

            M_PopMenu();
            DKM_Leave();
	        cls.key_dest = key_game;
        }
    }
}

void CMenuMain::Animate(int elapsed_time) {
    //go through each of the buttons.
    for (int i = 0; i < DKMB_NUM_BUTTONS; i++) {
        //get the button.
        CMenuSub *button = sub_menu[i];

        if (i == selection) {
            //let the sub-menu animate its controls and stuff.
            button->Animate(elapsed_time);
        }

        //add the elapsed time to the button's time.
        button->time += elapsed_time;

        //check our current status.
        if (button->status == BS_HILIGHT_FORWARD) {
            //we are going towards hilight.
            //check if we have enough time to get there.
            if (button->time >= ANIMATION_HILIGHT_TIME) {
                //we have enough time to go to the hilight status.
                button->status = BS_HILIGHT;

                //check if we are continuing.
                if (button->dest_status == BS_SELECTED) {
                    //continue to the select forward state.
                    button->status = BS_SELECT_FORWARD;

                    //subtract off the time.
                    button->time -= ANIMATION_HILIGHT_TIME;

                    //we will do the check for BS_SELECT_FORWARD below.
                }
            }
        }

        if (button->status == BS_SELECT_FORWARD) {
            //we are going towards select.
            //check if we are enough time to get there.
            if (button->time >= ANIMATION_SELECT_TIME) {
                //we have enough time to go to the hilight status.
                button->status = BS_SELECTED;
            }
        }

        if (button->status == BS_SELECT_BACKWARD) {
            //we are going towards hilight
            //check if we have enough time to get there.
            if (button->time >= ANIMATION_SELECT_TIME) {
                //we have enough time.
                button->status = BS_HILIGHT;

                //check if we are continuing.
                if (button->dest_status == BS_START) {
                    //continue to hilight backward state.
                    button->status = BS_HILIGHT_BACKWARD;

                    //subtract off the time.
                    button->time -= ANIMATION_SELECT_TIME;

                    //we will the check for BS_HILIGHT_BACKWARD below.
                }
            }
        }

        if (button->status == BS_HILIGHT_BACKWARD) {
            //we are going towards start
            //check if we have enough time.
            if (button->time >= ANIMATION_HILIGHT_TIME) {
                //we have enough time.
                button->status = BS_START;
            }
        }
    }

    //
    //animate the header/banner at the top of the screen.
    //

    //check if any buttons are down.
    if (selection == DKMB_NONE) {
        //no sub-menus are selected, check if we need to start closing the banner.
        if (banner_status == HS_OPEN || banner_status == HS_OPEN_WIGGLE) {
            //move to the closing state.
            banner_status = HS_CLOSING;

            //reset the time.
            banner_animation_time = 0;

            S_StartLocalSound("menus/Iron Plaque rotates up.wav");
        }
    }
    //there should be something showing on the banner.
    else if (banner_status == HS_CLOSED) {
        //move to the opening state.
        banner_status = HS_SLIDE_FORWARD;

        //make the banner display the correct mode.
        banner_text = selection;

        //reset the time.
        banner_animation_time = 0;

        S_StartLocalSound("menus/Iron Plaque rotates out.wav");
    }
    //if we are open and we are showing the wrong thing, close.
    else if ((banner_status == HS_OPEN || banner_status == HS_OPEN_WIGGLE) && selection != banner_text) {
        //go to the closing state.
        banner_status = HS_CLOSING;

        //reset the time.
        banner_animation_time = 0;

        S_StartLocalSound("menus/Iron Plaque rotates up.wav");
    }

    //add time to the banner animation.
    banner_animation_time += elapsed_time;

    //change banner states until we process all the elapsed time.
    //check if we are in one of the animated states.
    while ((banner_status == HS_SLIDE_FORWARD || banner_status == HS_OPENING || 
         banner_status == HS_CLOSING || banner_status == HS_OPEN_WIGGLE)) 
    {
        //check if the animation time is enough to finish the animation.
        if (banner_status == HS_OPEN_WIGGLE) {
            if (banner_animation_time >= ANIMATION_BANNER_WIGGLE) {
                //stop wiggling.
                banner_status = HS_OPEN;
            }
            else {
                //we are still wiggling.
                break;
            }
        }
        else if (banner_animation_time >= ANIMATION_BANNER_TURN) {
            //enough time has passed to finish the animation.
            //subtract off the time spent in the animation.
            banner_animation_time -= ANIMATION_BANNER_TURN;

            //check the state we are leaving.
            if (banner_status == HS_SLIDE_FORWARD) {
                //go to the opening state.
                banner_status = HS_OPENING;

                S_StartLocalSound("menus/Iron Plaque rotates out.wav");
            }
            else if (banner_status == HS_OPENING) {
                //we are now open.
                //check if we should stay open and go to our wiggle state, or just go directly to the closing state.
                if (banner_text == selection) {
                    //wiggle
                    banner_status = HS_OPEN_WIGGLE;
                }
                else {
                    //start closing.
                    banner_status = HS_CLOSING;

                    S_StartLocalSound("menus/Iron Plaque rotates up.wav");
                }
            }
            else if (banner_status == HS_CLOSING) {
                //we are now closed.
                banner_status = HS_CLOSED;
                banner_text = DKMB_NONE;
            }
        }
        else {
            //we are still in an animation.
            break;
        }

        //check if we are wiggling and need to start closing.
        if (banner_status == HS_OPEN_WIGGLE && banner_text != selection) {
            //start closing.
            banner_status = HS_CLOSING;
            //reset the animation time.
            banner_animation_time = 0;

            S_StartLocalSound("menus/Iron Plaque rotates up.wav");
        }
        //check if we are open and need to start closing.
        else if (banner_status == HS_OPEN && banner_text != selection) {
            //we need to close.
            banner_status = HS_CLOSING;

            S_StartLocalSound("menus/Iron Plaque rotates up.wav");
        }
        //check if we are closed and need to open.
        else if (banner_status == HS_CLOSED && selection != DKMB_NONE) {
            //we need to open.
            banner_status = HS_SLIDE_FORWARD;
            //make the banner display the correct text.
            banner_text = selection;

            S_StartLocalSound("menus/Iron Plaque rotates out.wav");
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Sub-Main Menu base class.
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSub::CMenuSub() {
    status = BS_START;
    time = 0;
    dest_status = BS_START;

    enabled = true;
}

#define FRAME_START 0
#define FRAME_HILIGHT 1
#define FRAME_SELECTED 15

int CMenuSub::Frame() {
    //check the status.
    float percent;
    switch (status) {
        case BS_START: {
            return FRAME_START;
        }
        case BS_HILIGHT_FORWARD: {
            percent = float(time) / ANIMATION_HILIGHT_TIME;
            bound_min(percent, 0.0f);
            bound_max(percent, 1.0f);
            return int(FRAME_START + (FRAME_HILIGHT - FRAME_START) * percent);
        }
        case BS_HILIGHT_BACKWARD: {
            percent = float(time) / ANIMATION_HILIGHT_TIME;
            bound_min(percent, 0.0f);
            bound_max(percent, 1.0f);
            percent = 1.0f - percent;
            return int(FRAME_START + (FRAME_HILIGHT - FRAME_START) * percent);
        }
        case BS_HILIGHT: {
            return FRAME_HILIGHT;
        }
        case BS_SELECT_FORWARD: {
            percent = float(time) / ANIMATION_SELECT_TIME;
            bound_min(percent, 0.0f);
            bound_max(percent, 1.0f);
            return int(FRAME_HILIGHT + (FRAME_SELECTED - FRAME_HILIGHT) * percent);
        }
        case BS_SELECT_BACKWARD: {
            percent = float(time) / ANIMATION_SELECT_TIME;
            bound_min(percent, 0.0f);
            bound_max(percent, 1.0f);
            percent = 1.0f - percent;
            return int(FRAME_HILIGHT + (FRAME_SELECTED - FRAME_HILIGHT) * percent);
        }
        case BS_SELECTED: {
            return FRAME_SELECTED;
        }
    }

    return 0;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Quit Menu
//
///////////////////////////////////////////////////////////////////////////////////

#define QUIT_YES_X 190
#define QUIT_YES_Y 230
#define QUIT_NO_X 290
#define QUIT_NO_Y 230

CMenuSubQuit::CMenuSubQuit() {
    yes.Init(QUIT_YES_X, QUIT_YES_Y, 60, 20, "/models/interface/unitbox/buttonmini4.bmp");
    no.Init(QUIT_NO_X, QUIT_NO_Y, 60, 20, "/models/interface/unitbox/buttonmini4.bmp");
}

void CMenuSubQuit::Enter() {
    //reset our buttons.
    yes.Reset();
    no.Reset();

}

void CMenuSubQuit::Animate(int elapsed_time) {
    //animate our buttons.
    yes.Animate(elapsed_time);
    no.Animate(elapsed_time);
}

void CMenuSubQuit::PlaceEntities() {
    yes.PlaceEntities();
    no.PlaceEntities();
}

void CMenuSubQuit::DrawForeground() {
//    DKM_DrawString(200, 210, "Suck it down (tm)?");

    DKM_DrawString(QUIT_YES_X + 20, QUIT_YES_Y + 5, "Yes");
    DKM_DrawString(QUIT_NO_X + 20, QUIT_NO_Y + 5, "No");
}

bool CMenuSubQuit::Keydown(int key) {
    switch (key) {
        case 'y':
            //quit the game.
            S_StartLocalSound("menus/yes_001.wav");
            menu.ShutdownString("quit\n");
            menu.Shutdown();
            return true;
        case 'n':
        case K_ESCAPE:
            //leave the quit menu.
            S_StartLocalSound("menus/no_001.wav");
            menu.Enter();
            return true;
    }

    return false;
}

bool CMenuSubQuit::MousePos(int x, int y) {
    bool ret = false;
    if (yes.MousePos(x, y) == true) ret = true;
    if (no.MousePos(x, y) == true) ret = true;

    return ret;
}

bool CMenuSubQuit::MouseDown(int x, int y) {
    if (yes.MouseDown(x, y) == true) return true;
    if (no.MouseDown(x, y) == true) return true;

    return false;
}

bool CMenuSubQuit::MouseUp(int x, int y) {
    if (yes.MouseUp(x, y) == true) {
        //they clicked yes.
        S_StartLocalSound("menus/yes_001.wav");
        menu.ShutdownString("quit\n");
        menu.Shutdown();
        return true;
    }
    if (no.MouseUp(x, y) == true) {
        //they clicked no.
        S_StartLocalSound("menus/no_001.wav");
        menu.Enter();
        return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Help Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubHelp::CMenuSubHelp() {
    enabled = false;
}

void CMenuSubHelp::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubHelp::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Demos Menu
//
///////////////////////////////////////////////////////////////////////////////////

#define DEMOS_BUTTON_X 130
#define demos_button_y(i) (155 + (i) * 21)
#define DEMOS_BUTTON_W 290
#define DEMOS_BUTTON_H 15

typedef struct {
    char *display_name;
    char *map_name;
} demos_start;

demos_start demos[NUM_DEMOS] = {
    {"Episode 1: The Sewer", "map e1m2.dm2\n"},
    {"Episode 1: The Slammer", "map e1m3.dm2\n"},
    {"Episode 2: Athens", "map e2m3.dm2\n"},
    {"Episode 2: Minos' Palace", "map e2m6.dm2\n"},
    {"Episode 3: Gharroth's Castle", "map e3m3.dm2\n"},
    {"Episode 3: Wyndrax's Tower", "map e3m4.dm2\n"},
    {"Episode 4: Escape From Alcatraz", "map e4m1b.dm2\n"},
    {"Episode 4: Mishima's Hideout", "map e4m8.dm2\n"},
};

CMenuSubDemos::CMenuSubDemos() {
    //position the buttons.
    for (int i = 0; i < NUM_DEMOS; i++) {
        start[i].Init(DEMOS_BUTTON_X, demos_button_y(i), DEMOS_BUTTON_W, DEMOS_BUTTON_H, "/models/interface/unitbox/minixl.bmp");
    }
}

void CMenuSubDemos::Enter() {

}

void CMenuSubDemos::Animate(int elapsed_time) {
    for (int i = 0; i < NUM_DEMOS; i++) {
        start[i].Animate(elapsed_time);
    }
}

void CMenuSubDemos::DrawForeground() {
    for (int i = 0; i < NUM_DEMOS; i++) {
        int len = strlen(demos[i].display_name);

        DKM_DrawString(DEMOS_BUTTON_X + DEMOS_BUTTON_W / 2 - len / 2 * 8, demos_button_y(i) + 2, demos[i].display_name);
    }
}

void CMenuSubDemos::PlaceEntities() {
    for (int i = 0; i < NUM_DEMOS; i++) {
        start[i].PlaceEntities();
    }
}

bool CMenuSubDemos::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

bool CMenuSubDemos::MousePos(int x, int y) {
    bool ret = false;
    //check each button.
    for (int i = 0; i < NUM_DEMOS; i++) {
        if (start[i].MousePos(x, y) == true) ret = true;
    }   

    return ret;
}

bool CMenuSubDemos::MouseDown(int x, int y) {
    for (int i = 0; i < NUM_DEMOS; i++) {
        if (start[i].MouseDown(x, y) == true) return true;
    }

    return false;
}

bool CMenuSubDemos::MouseUp(int x, int y) {
    for (int i = 0; i < NUM_DEMOS; i++) {
        if (start[i].MouseUp(x, y) == true) {
            //they clicked a button.
            menu.ShutdownString("deathmatch 0\ncoop 0\nloading\nkillserver\nwait\nwait\nwait\nwait\nwait\nwait\n%s\n", demos[i].map_name);
            menu.Shutdown();
            return true;
        }
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Advanced Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubAdvanced::CMenuSubAdvanced() {
    enabled = false;
}

void CMenuSubAdvanced::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubAdvanced::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  General Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubGeneral::CMenuSubGeneral() {
    enabled = false;
}

void CMenuSubGeneral::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubGeneral::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Joystick Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubJoystick::CMenuSubJoystick() {
    enabled = false;
}

void CMenuSubJoystick::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubJoystick::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Keyboard Menu
//
///////////////////////////////////////////////////////////////////////////////////

//structure that holds info on the bindings that for the commands.
class command_binding {
public:
    command_binding(const char *name, const char *text);

    //the name of the command that is displayed to the user.
    char command_name[64];

    //the name of the command that is sent to perform the action.
    char command_text[64];

    //the keys that are bound to this command.
    int keys[2];
};

//the different commands we can set in the keyboard config menu
typedef enum {
    KCC_FORWARD,
    KCC_BACKWARD,
    KCC_STEP_LEFT,
    KCC_STEP_RIGHT,
    KCC_ATTACK,
    KCC_UP_JUMP,
    KCC_DOWN_CROUCH,
    KCC_WEAPON_NEXT,
    KCC_WEAPON_PREV,
    KCC_TURN_LEFT,
    KCC_TURN_RIGHT,
    KCC_RUN,
    KCC_SIDESTEP,
    KCC_LOOK_UP,
    KCC_LOOK_DOWN,
    KCC_CENTER_VIEW,
    KCC_MOUSE_LOOK,
    KCC_KEYBOARD_LOOK,
    KCC_INV_OPEN,
    KCC_INV_USE,
    KCC_INV_DROP,
    KCC_INV_NEXT,
    KCC_INV_PREV,
//    KCC_WEAPON_1,
//    KCC_WEAPON_2,
//    KCC_WEAPON_3,
//    KCC_WEAPON_4,
//    KCC_WEAPON_5,
//    KCC_WEAPON_6,
//    KCC_WEAPON_7,
//    KCC_WEAPON_8,
//    KCC_WEAPON_9,

    KCC_NUM_COMMANDS,
} key_config_command;

//the command bindings we have.
command_binding *bindings[KCC_NUM_COMMANDS];

//searches through the user's key bindings and fills in the key fields in the bindings array.
void FindKeyBindings() {
    //go through each of our commands.
    for (int i = 0; i < KCC_NUM_COMMANDS; i++) {
        //the number of bindings we have found for this command.
        int num_found = 0;

        bindings[i]->keys[0] = -1;
        bindings[i]->keys[1] = -1;

        //search through the bindings
        for (int key = 0; key < 256; key++) {
            //don't check any mouse or joystick keys.
            if (key >= 200 && key != K_PAUSE) continue;
            if (keybindings[key] == NULL) continue;

            //compare the binding with the command text.
            if (stricmp(bindings[i]->command_text, keybindings[key]) == 0) {
                //a match.
                bindings[i]->keys[num_found] = key;
                num_found++;

                if (num_found > 1) break;
            }
        }

        //go to the next command.
    }
}

command_binding::command_binding(const char *name, const char *text) {
    //put in default values.
    command_name[0] = '\0';
    command_text[0] = '\0';
    keys[0] = keys[1] = -1;

    //check our parameters.
    if (name == NULL) return;
    if (text == NULL) return;

    //fill in the command name and text.
    strncpy(command_name, name, 63);
    strncpy(command_text, text, 63);

    //make sure the strings are null terminated.
    command_name[63] = '\0';
    command_text[63] = '\0';
}

#define KEY_COL0_X 90
#define KEY_COL1_X 260
#define KEY_COL2_X 370
#define key_line_y(i) (175 + (i) * 10)
#define KEY_NUM_VISIBLE_LINES 19
#define KEY_LISTBOX_UP_X 250
#define KEY_LISTBOX_UP_Y key_line_y(KEY_NUM_VISIBLE_LINES + 1.5)
#define KEY_LISTBOX_DOWN_X KEY_LISTBOX_UP_X + 60
#define KEY_LISTBOX_DOWN_Y key_line_y(KEY_NUM_VISIBLE_LINES + 1.5)
#define KEY_BORDER_LEFT (KEY_COL0_X - 10)
#define KEY_BORDER_TOP key_line_y(-1)
#define KEY_BORDER_RIGHT (KEY_COL2_X + KEY_COL2_X - KEY_COL1_X)
#define KEY_BORDER_BOTTOM key_line_y(KEY_NUM_VISIBLE_LINES + 0.7)

#define KEY_DEFAULTS_X KEY_BORDER_LEFT
#define KEY_DEFAULTS_Y KEY_LISTBOX_DOWN_Y

CMenuSubKeyboard::CMenuSubKeyboard() {
    defaults.Init(KEY_DEFAULTS_X, KEY_DEFAULTS_Y, 90, 20, "/models/interface/unitbox/minim.bmp");
    listbox_up.Init(KEY_LISTBOX_UP_X, KEY_LISTBOX_UP_Y, 50, 20, "/models/interface/unitbox/miniup.bmp");
    listbox_down.Init(KEY_LISTBOX_DOWN_X, KEY_LISTBOX_DOWN_Y, 50, 20, "/models/interface/unitbox/minidown.bmp");
    border.Init(KEY_BORDER_LEFT, KEY_BORDER_TOP, KEY_BORDER_RIGHT, KEY_BORDER_BOTTOM, 2);

    TopIndex(0);

    hilight_showing = true;
    hilight_row = hilight_col = 0;

    assigning_control = false;
    hilight_animation_time = 0;

    //initialize the command bindings.
    bindings[KCC_FORWARD] = new command_binding("Move Forward", "+forward");
    bindings[KCC_BACKWARD] = new command_binding("Move Backward", "+back");
    bindings[KCC_STEP_LEFT] = new command_binding("Step Left", "+moveleft");
    bindings[KCC_STEP_RIGHT] = new command_binding("Step Right", "+moveright");
    bindings[KCC_ATTACK] = new command_binding("Attack", "+attack");
    bindings[KCC_UP_JUMP] = new command_binding("Jump/Move Up", "+moveup");
    bindings[KCC_DOWN_CROUCH] = new command_binding("Crouch/Move Down", "+movedown");
    bindings[KCC_WEAPON_NEXT] = new command_binding("Next Weapon", "weapnext");
    bindings[KCC_WEAPON_PREV] = new command_binding("Prev Weapon", "weapprev");
    bindings[KCC_TURN_LEFT] = new command_binding("Turn Left", "+left");
    bindings[KCC_TURN_RIGHT] = new command_binding("Turn Right", "+right");
    bindings[KCC_RUN] = new command_binding("Run", "+speed");
    bindings[KCC_SIDESTEP] = new command_binding("Sidestep", "+strafe");
    bindings[KCC_LOOK_UP] = new command_binding("Look Up", "+lookup");
    bindings[KCC_LOOK_DOWN] = new command_binding("Look Down", "+lookdown");
    bindings[KCC_CENTER_VIEW] = new command_binding("Center View", "centerview");
    bindings[KCC_MOUSE_LOOK] = new command_binding("Mouse Look", "+mlook");
    bindings[KCC_KEYBOARD_LOOK] = new command_binding("Keyboard Look", "+klook");
    bindings[KCC_INV_OPEN] = new command_binding("Inventory Open", "inven");
    bindings[KCC_INV_USE] = new command_binding("Use Item", "use");
    bindings[KCC_INV_DROP] = new command_binding("Drop Item", "invdrop");
    bindings[KCC_INV_NEXT] = new command_binding("Next Item", "invnext");
    bindings[KCC_INV_PREV] = new command_binding("Prev Item", "invprev");
}

void CMenuSubKeyboard::TopIndex(int index) {
    if (index < 0) {
        index = 0;   
    }
    else if (index + KEY_NUM_VISIBLE_LINES > KCC_NUM_COMMANDS) {
        index = KCC_NUM_COMMANDS - KEY_NUM_VISIBLE_LINES;
    }

    top_index = index;

    //check if the up button should be visible.
    if (top_index > 0) {
        show_up_button = true;
    }   
    else {
        show_up_button = false;
    }

    //check if the down button should be visible.
    if (top_index + KEY_NUM_VISIBLE_LINES >= KCC_NUM_COMMANDS) {
        show_down_button = false;
    }
    else {
        show_down_button = true;
    }
}

//row 0 is the top row that is showing.
void CMenuSubKeyboard::Hilight(int row, int column) {
    if (column < 0 || column > 1 || row < 0 || row >= KEY_NUM_VISIBLE_LINES) {
        //bad row/column. un-hilight everything.
        hilight_showing = false;
        return;
    }

    hilight_showing = true;

    hilight_row = row;
    hilight_col = column;

    //size the hilight box.
    HilightSize(0);
}

#define KEY_ANIM_PERIOD 200.0f
#define KEY_ANIM_AMPLITUDE 20.0f

void CMenuSubKeyboard::HilightSize(int time) {
    //compute the change in the hilight box size.  //make a sin wave that goes 0-1-0-1-0
    float box_change = KEY_ANIM_AMPLITUDE * (1 - (cos(M_PI * 2 * time / KEY_ANIM_PERIOD) * 0.5f + 0.5f));

    if (hilight_col == 0) {
        hilight_box.Init(KEY_COL1_X - 3 + box_change, key_line_y(hilight_row) - 3, 
                         KEY_COL2_X - 1 - box_change, key_line_y(hilight_row + 1), 2);
    }
    else {
        hilight_box.Init(KEY_COL2_X - 3 + box_change, key_line_y(hilight_row) - 3, 
                         KEY_BORDER_RIGHT - 7 - box_change, key_line_y(hilight_row + 1), 2);
    }
}

void CMenuSubKeyboard::AssignStart() {
    if (hilight_showing == false) return;

    //set our flag.
    assigning_control = true;
    //reset the animation time.
    hilight_animation_time = 0;

    //play a sound.
    S_StartLocalSound("menus/button_005.wav");
}

void CMenuSubKeyboard::AssignAbort() {
    assigning_control = false;

    HilightSize(0);

    S_StartLocalSound("menus/button_005.wav");
}

void CMenuSubKeyboard::AssignDelete() {
    HilightSize(0);

    //get the command number we are chaning.
    int command_num = top_index + hilight_row;

    //paranoia strikes:
    if (command_num < 0 || command_num >= KCC_NUM_COMMANDS) {
        return;
    }

    //get the key that is selected.
    int oldkey = (hilight_col == 0) ?
                    bindings[top_index + hilight_row]->keys[0] :
                    bindings[top_index + hilight_row]->keys[1];

    //delete the binding for this key
    Key_SetBinding(oldkey, "");

    //we need to re-find our bindings.
    FindKeyBindings();

    assigning_control = false;

    S_StartLocalSound("menus/button_005.wav");
}

void CMenuSubKeyboard::AssignComplete(int key) {
    //whatever happens now, we are done assigning a key.
    assigning_control = false;

    //fix the size of our hilight box.
    HilightSize(0);

    //get the command number we are chaning.
    int command_num = top_index + hilight_row;

    //paranoia strikes:
    if (top_index + hilight_row < 0 || top_index + hilight_row >= KCC_NUM_COMMANDS) {
        return;
    }

    //ok, we have a key that we are going to assign to this command.
    //get the old key.
    int oldkey = (hilight_col == 0) ?
                    bindings[top_index + hilight_row]->keys[0] :
                    bindings[top_index + hilight_row]->keys[1];

    //the other key that is assigned to this command.
    int secondkey = (hilight_col != 0) ?
                        bindings[top_index + hilight_row]->keys[0] :
                        bindings[top_index + hilight_row]->keys[1];
                    
    //check if the new key is different from the old one.
    if (key == oldkey || key == secondkey) {
        //nothing changing.
        return;
    }

    //unbind the old key.
    Key_SetBinding(oldkey, "");

    //set the new key.
    Key_SetBinding(key, bindings[top_index + hilight_row]->command_text);

    //we need to re-find our bindings.
    FindKeyBindings();

    S_StartLocalSound("menus/button_005.wav");
}

void CMenuSubKeyboard::Enter() {
    defaults.Reset();
    listbox_up.Reset();
    listbox_down.Reset();

    FindKeyBindings();

    //refresh the hilight size.
    if (hilight_showing == true) {
        Hilight(hilight_row, hilight_col);
    }
}

void CMenuSubKeyboard::Animate(int elapsed_time) {
    defaults.Animate(elapsed_time);
    listbox_up.Animate(elapsed_time);
    listbox_down.Animate(elapsed_time);

    //if we are assigning a control, we make the hilight box throb.
    if (assigning_control == true) {
        //add time to our counter.
        hilight_animation_time += elapsed_time;
        
        //size the hilight box.
        HilightSize(hilight_animation_time);
    }
}

char *KeyString(int key) {
    if (key == 1000) {
        //we are configing this slot, return a string with ???'s that will be displayed.
        return "     ???";
    }

    return Key_KeynumToString(key);
}

void CMenuSubKeyboard::DrawForeground() {
    //draw text on the defaults button.
    DKM_DrawString(KEY_DEFAULTS_X + 20, KEY_DEFAULTS_Y + 5, "Defaults");

    if (assigning_control == false) {
        //draw text on the listbox up/down buttons.
//        if (show_up_button == true) DKM_DrawString(KEY_LISTBOX_UP_X + 10, KEY_LISTBOX_UP_Y + 5, "Up");
//        if (show_down_button == true) DKM_DrawString(KEY_LISTBOX_DOWN_X + 10, KEY_LISTBOX_DOWN_Y + 5, "Down");

        if (hilight_showing == true) {
            DKM_DrawString(KEY_COL0_X, key_line_y(-3), "Enter to set, DEL to delete.");
        }
        else {
            DKM_DrawString(KEY_COL0_X, key_line_y(-3), "Click or Enter to select.");
        }
    }
    else {
        DKM_DrawString(KEY_COL0_X, key_line_y(-3), "Hit key to set, ESC to abort, Mouse2 to delete.");
    }

    //draw the main listing of commands.
    for (int i = 0; i <  KEY_NUM_VISIBLE_LINES; i++) {
        int index = i + top_index;
        //do an error check just for paranoia's sake.
        if (index >= KCC_NUM_COMMANDS) break;

        //draw the command name.
        DKM_DrawString(KEY_COL0_X, key_line_y(i), bindings[index]->command_name);

        //draw the 2 keys that are mapped to the command.
        int key1 = bindings[index]->keys[0];
        int key2 = bindings[index]->keys[1];

        //check if we are configing a key.
        if (assigning_control && i == hilight_row) {
            if (hilight_col == 0) key1 = 1000;
            if (hilight_col == 1) key2 = 1000;
        }

        if (key1 != -1) {
            DKM_DrawString(KEY_COL1_X, key_line_y(i), KeyString(key1));
        }
        if (key2 != -1) {
            DKM_DrawString(KEY_COL2_X, key_line_y(i), KeyString(key2));
        }
    }
}

void CMenuSubKeyboard::PlaceEntities() {
    defaults.PlaceEntities();

    if (assigning_control == false) {
        //make the list box buttons.
        if (show_up_button == true) listbox_up.PlaceEntities();
        if (show_down_button == true) listbox_down.PlaceEntities();
    }

    border.PlaceEntities();

    if (hilight_showing == true) {
        hilight_box.PlaceEntities();
    }
}

bool CMenuSubKeyboard::Keydown(int key) {
    if (assigning_control == true) {
        //if mouse2 was clicked, we delete the assignment.
        if (key == K_MOUSE2) {
            AssignDelete();
            return true;
        }

        //dont allow keys that arent from the keyboard.
        if (key >= 200 && key != K_PAUSE) {
            //we dont allow assigning of those keys in the keyboard menu.
            return true;
        }

        switch (key) {
            case K_ESCAPE:
                //abort our assign process.
                AssignAbort();
                return true;
            //filter any keys that arent allowed to be configured.
            case '`':
                return true;
        }

        //finish the assignment.
        AssignComplete(key);

        return true;
    }

    if (hilight_showing == true) {
        switch (key) {
            case K_ESCAPE:
                //unhilight whatever we are doing.
                Hilight(-1, -1);
                return true;
            case K_BACKSPACE:
            case K_DEL:
                //delete the key we are selecting.
                AssignDelete();
                return true;
            case K_UPARROW:
                //if we are hilighting something at the top, scroll up.
                if (hilight_row < 1) {
                    TopIndex(top_index - 1);
                }
                else {
                    //move the hilight up a row.
                    Hilight(hilight_row - 1, hilight_col);
                }

                S_StartLocalSound("menus/button_004.wav");
                return true;
            case K_DOWNARROW:
                //if we are hilighting on the bottom row, scroll down.
                if (hilight_row >= KEY_NUM_VISIBLE_LINES - 1) {
                    TopIndex(top_index + 1);
                }
                else {
                    Hilight(hilight_row + 1, hilight_col);
                }

                S_StartLocalSound("menus/button_004.wav");
                return true;
            case K_RIGHTARROW:
                if (hilight_col == 0) {
                    Hilight(hilight_row, 1);
                }

                S_StartLocalSound("menus/button_004.wav");
                return true;
            case K_LEFTARROW:
                if (hilight_col == 1) {
                    Hilight(hilight_row, 0);
                }

                S_StartLocalSound("menus/button_004.wav");
                return true;
            case K_ENTER:
                AssignStart();
                return true;
            case K_PGDN: {
                //get the control we want to be hilighted.
                int index;
                index = top_index + hilight_row + 5;
                bound_max(index, KCC_NUM_COMMANDS - 1);
                //check if that index is shown on the screen.
                if (index >= top_index + KEY_NUM_VISIBLE_LINES) {
                    //it is off the bottom.  figure out how many lines we need to scroll down to reach it.
                    int newtop;
                    newtop = index - KEY_NUM_VISIBLE_LINES + 1;
                    //scroll to that top index.
                    TopIndex(newtop);

                    //put the selection on the bottom column.
                    Hilight(KEY_NUM_VISIBLE_LINES - 1, hilight_col);
                }
                else {
                    //move the hilight to hilight that command.
                    Hilight(index - top_index, hilight_col);
                }

                S_StartLocalSound("menus/button_004.wav");
                return true;
            }
            case K_PGUP:
                //get the control we want to be hilighted.
                int index;
                index = top_index + hilight_row - 5;
                bound_min(index, 0);
                //check if that index is shown on the screen.
                if (index < top_index) {
                    //it is off the top.  set the new top index to the index we want to see.
                    TopIndex(index);

                    //hilight the top row.
                    Hilight(0, hilight_col);
                }
                else {
                    //move the hilight to hilight that command.
                    Hilight(index - top_index, hilight_col);
                }

                S_StartLocalSound("menus/button_004.wav");
                return true;
        }
        return false;
    }

    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
        case K_UPARROW:
            TopIndex(top_index - 1);
            return true;
        case K_DOWNARROW:
            TopIndex(top_index + 1);
            return true;
        case K_PGUP:
            TopIndex(top_index - KEY_NUM_VISIBLE_LINES / 2);
            return true;
        case K_PGDN:
            TopIndex(top_index + KEY_NUM_VISIBLE_LINES / 2);
            return true;
        case K_ENTER:
            Hilight(0, 0);
            return true;
    }

    return false;
}

bool CMenuSubKeyboard::MousePos(int x, int y) {
    bool ret = false;
    if (defaults.MousePos(x, y) == true) ret = true;

    if (assigning_control == false) {
        if (show_up_button == true && listbox_up.MousePos(x, y) == true) ret = true;
        if (show_down_button == true && listbox_down.MousePos(x, y) == true) ret = true;
    }

    return ret;
}

bool CMenuSubKeyboard::MouseDown(int x, int y) {
    if (defaults.MouseDown(x, y) == true) return true;

    if (assigning_control == false) {
        if (show_up_button == true && listbox_up.MouseDown(x, y) == true) return true;
        if (show_down_button == true && listbox_down.MouseDown(x, y) == true) return true;
    }

    //check if we are over one of the 2 columns of key assignments.
    if (x >= KEY_COL1_X - 3 &&
        x <= KEY_BORDER_RIGHT - 7 &&
        y >= key_line_y(0) - 3 &&
        y <= key_line_y(KEY_NUM_VISIBLE_LINES))
    {
        //we clicked on a key.
        //get the column we clicked on.
        int column = (x >= KEY_COL2_X - 3) ? 1 : 0;

        //get the row that was clicked on.
        int row = float(y - (key_line_y(0) - 3)) / (key_line_y(KEY_NUM_VISIBLE_LINES) - (key_line_y(0) - 3)) * KEY_NUM_VISIBLE_LINES;
        bound_max(row, KEY_NUM_VISIBLE_LINES - 1);
        bound_min(row, 0);

        //Hilight this item.
        Hilight(row, column);

        //set our flag that we are assigning this control.
        AssignStart();
    }

    return false;
}

bool CMenuSubKeyboard::MouseUp(int x, int y) {
    if (defaults.MouseUp(x, y) == true) {
        //they clicked defaults.
        return true;
    }

    if (assigning_control == false) {
        if (show_up_button == true && listbox_up.MouseUp(x, y) == true) {
            //they clicked the listbox up button.
            TopIndex(top_index - 1);
            return true;
        }
        if (show_down_button == true && listbox_down.MouseUp(x, y) == true) {
            //they clicked the listbox down button.
            TopIndex(top_index + 1);
            return true;
        }
    }

    return false;
}


///////////////////////////////////////////////////////////////////////////////////
//
//  Mouse Menu
//
///////////////////////////////////////////////////////////////////////////////////

#define MOUSE_ACTION_LEFT 80
#define MOUSE_ACTION_TOP 165
#define MOUSE_ACTION_RIGHT 330
#define MOUSE_ACTION_BOTTOM 233
#define mouse_line_y(i) (MOUSE_ACTION_TOP + ((i) + 1) * 10)
#define MOUSE_COL0_X (MOUSE_ACTION_LEFT + 0)
#define MOUSE_COL1_X ((MOUSE_ACTION_RIGHT + MOUSE_ACTION_LEFT) / 2 - 30)
#define MOUSE_REVERSE_Y (MOUSE_ACTION_BOTTOM + 10)
#define MOUSE_SENSITIVITY_X (MOUSE_ACTION_LEFT)
#define MOUSE_SENSITIVITY_Y (MOUSE_REVERSE_Y + 40)
#define MOUSE_SENSITIVITY_W 235
#define MOUSE_NUM_COMMANDS_VISIBLE 20
#define MOUSE_COMMAND_LEFT (MOUSE_ACTION_RIGHT + 5)
#define MOUSE_COMMAND_TOP MOUSE_ACTION_TOP
#define MOUSE_COMMAND_RIGHT (MOUSE_COMMAND_LEFT + 140)
#define MOUSE_COMMAND_BOTTOM (mouse_line_y(MOUSE_NUM_COMMANDS_VISIBLE + 1))
#define MOUSE_COMMAND_UP_X (MOUSE_COMMAND_LEFT + 10)
#define MOUSE_COMMAND_UP_Y (MOUSE_COMMAND_BOTTOM + 5)
#define MOUSE_COMMAND_UP_W ((MOUSE_COMMAND_RIGHT - MOUSE_COMMAND_LEFT) / 2 - 20)
#define MOUSE_COMMAND_DOWN_X (MOUSE_COMMAND_LEFT + MOUSE_COMMAND_UP_W + 30)
#define MOUSE_COMMAND_DOWN_Y (MOUSE_COMMAND_UP_Y)
#define MOUSE_COMMAND_DOWN_W (MOUSE_COMMAND_UP_W)

CMenuSubMouse::CMenuSubMouse() {
    //place the border objects.
    action_box.Init(MOUSE_ACTION_LEFT, MOUSE_ACTION_TOP, MOUSE_ACTION_RIGHT, MOUSE_ACTION_BOTTOM, 2);
    command_box.Init(MOUSE_COMMAND_LEFT, MOUSE_COMMAND_TOP, MOUSE_COMMAND_RIGHT, MOUSE_COMMAND_BOTTOM, 2);
    reverse_axis.Init(MOUSE_ACTION_LEFT, MOUSE_ACTION_BOTTOM + 10, 140, 15, "/models/interface/unitbox/minil.bmp");

    //place the command box up and down buttons.
    command_up.Init(MOUSE_COMMAND_UP_X, MOUSE_COMMAND_UP_Y, MOUSE_COMMAND_UP_W, 20, "/models/interface/unitbox/miniup.bmp");
    command_down.Init(MOUSE_COMMAND_DOWN_X, MOUSE_COMMAND_DOWN_Y, MOUSE_COMMAND_DOWN_W, 20, "/models/interface/unitbox/minidown.bmp");

    sensitivity.Init(MOUSE_SENSITIVITY_X, MOUSE_SENSITIVITY_Y, MOUSE_SENSITIVITY_W, 20);
    sensitivity.InitExp(42, -2, 1);

    for (int i = 0; i < 5; i++) {
        mouse_bindings[i] = "None";
    }

    action_hilight = MA_NONE;
    selection = MA_NONE;

    num_command_items = 0;

    TopIndex(0);
}

extern "C" cvar_t *lookstrafe;
extern "C" cvar_t *freelook;
extern "C" cvar_t *m_pitch;

char *FindCommandName(const char *command_text) {
    for (int i = 0; i < KCC_NUM_COMMANDS; i++) {
        //compare the command text to the keybinding.
        if (stricmp(command_text, bindings[i]->command_text) == 0) {
            //we found a match.
            return bindings[i]->command_name;
        }
    }

    //couldn't find a nice command name for this text.
    return "Custom";
}

void CMenuSubMouse::FindBindings() {
    //find what the x axis is configured for.
    if (lookstrafe->value != 0) {
        mouse_bindings[MA_X_AXIS] = "Move Left/Right";
    }
    else {
        mouse_bindings[MA_X_AXIS] = "Turn Left/Right";
    }

    //find what the y axis is configured for.
    if (freelook->value != 0) {
        mouse_bindings[MA_Y_AXIS] = "Look Up/Down";
    }
    else {
        mouse_bindings[MA_Y_AXIS] = "Move For./Back.";
    }

    //look for the commands that the 3 mouse buttons are set to.
    for (int i = 0; i < 3; i++) {
        //check if anything is set to this mouse button.
        if (keybindings[K_MOUSE1 + i] == NULL || keybindings[K_MOUSE1 + i][0] == '\0') {
            //no binding for this mouse button.
            mouse_bindings[MA_BUTTON1 + i] = "None";
            continue;
        }

        mouse_bindings[MA_BUTTON1 + i] = FindCommandName(keybindings[K_MOUSE1 + i]);
    }
}

void CMenuSubMouse::SelectAction(mouse_action action) {
    selection = action;
    num_command_items = 0;
    command_hilight = -1;

    if (selection == MA_NONE) return;

    //get the number of items that we will display in the command list.
    if (selection == MA_X_AXIS || selection == MA_Y_AXIS) {
        //only 2 choices for the axis actions.
        num_command_items = 2;
    }
    else {
        //we have as many commands for the buttons as we have commands in the keyboard config menu.
        num_command_items = KCC_NUM_COMMANDS;
    }

    //if we have selected an axis, set the position of our sensitivity slider.
    if (selection == MA_X_AXIS) {
        //check what the value is set at currently.
        if (lookstrafe->value != 0) {
            //they are strafing.
            sensitivity.Position(fabs(m_side->value));
        }
        else {
            //they are turning
            sensitivity.Position(fabs(m_yaw->value));
        }
    }
    if (selection == MA_Y_AXIS) {
        if (freelook->value != 0) {
            //they are looking
            sensitivity.Position(fabs(m_pitch->value));
        }
        else {
            //they are moving
            sensitivity.Position(fabs(m_forward->value));
        }
    }
}

void CMenuSubMouse::SelectCommand(int index) {
    if (selection == MA_NONE) return;

    //check what action is selected.
    if (selection == MA_X_AXIS) {
        //check the index
        if (index == 0) {
            //they selected turn l/r.  set lookstrafe to 0.
            Cvar_SetValue("lookstrafe", 0);
        }
        else if (index == 1) {
            //they selected strafe l/r.  set lookstrafe to 1.
            Cvar_SetValue("lookstrafe", 1);
        }
        else {
            //bad index.
            return;
        }
    }
    else if (selection == MA_Y_AXIS) {
        //check the index.
        if (index == 0) {
            //they selected look up/d.  set freelook to 1.
            Cvar_SetValue("freelook", 1);
        }
        else if (index == 1) {
            //they selected move f/b.  set freelook to 0
            Cvar_SetValue("freelook", 0);
        }
        else {
            //bad index
            return;
        }
    }
    //make sure index is valid as a choice for one of the buttons.
    else if (index < -1 || index >= KCC_NUM_COMMANDS) {
        //bad index.
    }
    else if (selection == MA_BUTTON1) {
        if (index == -1) {
            Key_SetBinding(K_MOUSE1, "");
        }
        else {
            Key_SetBinding(K_MOUSE1, bindings[index]->command_text);
        }
    }
    else if (selection == MA_BUTTON2) {
        if (index == -1) {
            Key_SetBinding(K_MOUSE2, "");
        }
        else {
            Key_SetBinding(K_MOUSE2, bindings[index]->command_text);
        }
    }
    else if (selection == MA_BUTTON3) {
        if (index == -1) {
            Key_SetBinding(K_MOUSE3, "");
        }
        else {
            Key_SetBinding(K_MOUSE3, bindings[index]->command_text);
        }
    }

    //update our info on the bindings that are set.
    FindBindings();

    SelectAction(selection);
}

void CMenuSubMouse::TopIndex(int index) {
    //assume the whole command list isn't even showing for now.
    show_up_button = false;
    show_down_button = false;
    top_index = 0;

    if (selection == MA_NONE) return;

    //assume we were passed a valid index.
    top_index = index;

    //assume we see the down button for now.
    show_down_button = true;

    //make sure we arent scrolling down too far.
    if (top_index >= num_command_items - MOUSE_NUM_COMMANDS_VISIBLE) {
        top_index = num_command_items - MOUSE_NUM_COMMANDS_VISIBLE;

        //we are at the bottom of the list, down show the down button.
        show_down_button = false;
    }

    bound_min(top_index, 0);

    //if the top index is > 0, we should show the up button.
    if (top_index > 0) {
        show_up_button = true;
    }
    else {
        show_up_button = false;
    }
}

mouse_action CMenuSubMouse::HitAction(int x, int y) {
    //check if it is on any of them.
    if (x < MOUSE_COL0_X || x > MOUSE_ACTION_RIGHT - 5 || y < mouse_line_y(0) || y > mouse_line_y(MA_NUM_ACTIONS)) {
        //we're not even on the box
        return MA_NONE;
    }

    //it is in one of them.  get the percentage down we are.
    float percent = float(y - mouse_line_y(0)) / float(mouse_line_y(MA_NUM_ACTIONS) - mouse_line_y(0));

    int index = int(percent * MA_NUM_ACTIONS);

    bound_min(index, 0);
    bound_max(index, MA_BUTTON3);

    return (mouse_action)index;
}

//returns the line in the command list under the given coordinate, or -1 if the coordinate is off the list.
int CMenuSubMouse::HitCommand(int x, int y) {
    //check if it is the box at all.
    if (x < MOUSE_COMMAND_LEFT || x > MOUSE_COMMAND_RIGHT || 
        y < mouse_line_y(0) || y > mouse_line_y(MOUSE_NUM_COMMANDS_VISIBLE)) 
    {
        //hit nothing.
        return -1;
    }

    //get the percentage down we are at.
    float percent = float(y - mouse_line_y(0)) / float(mouse_line_y(MOUSE_NUM_COMMANDS_VISIBLE) - mouse_line_y(0));

    //get the index of the command we selected.
    int index = int(percent * MOUSE_NUM_COMMANDS_VISIBLE);

    //make sure its a valid index.
    if (index < 0 || index + top_index >= num_command_items) {
        //not a valid index.
        return -1;
    }

    //return the index we clicked
    return index + top_index;
}

void CMenuSubMouse::PositionActionHilight(int index) {
    if (index < 0 || index >= MA_NUM_ACTIONS) return;

    action_hilight_box.Init(MOUSE_COL1_X - 3, mouse_line_y(index) - 3, MOUSE_ACTION_RIGHT - 6, mouse_line_y(index + 1), 2);
}

void CMenuSubMouse::PositionCommandHilight(int index) {
    if (index < 0 || index >= MOUSE_NUM_COMMANDS_VISIBLE) return;

    command_hilight_box.Init(MOUSE_COMMAND_LEFT + 7, mouse_line_y(index) - 3, MOUSE_COMMAND_RIGHT - 7, mouse_line_y(index + 1), 2);
}

void CMenuSubMouse::Enter() {
    command_up.Reset();
    command_down.Reset();
    reverse_axis.Reset();

    action_hilight = MA_NONE;
    selection = MA_NONE;

    FindBindings();
}

void CMenuSubMouse::Animate(int elapsed_time) {
    sensitivity.Animate(elapsed_time);

    command_up.Animate(elapsed_time);
    command_down.Animate(elapsed_time);

    reverse_axis.Animate(elapsed_time);
}

void CMenuSubMouse::DrawForeground() {
    //draw the mouse controls.
    DKM_DrawString(MOUSE_COL0_X, mouse_line_y(0), "X Axis");
    DKM_DrawString(MOUSE_COL0_X, mouse_line_y(1), "Y Axis");
    DKM_DrawString(MOUSE_COL0_X, mouse_line_y(2), "Button 1");
    DKM_DrawString(MOUSE_COL0_X, mouse_line_y(3), "Button 2");
    DKM_DrawString(MOUSE_COL0_X, mouse_line_y(4), "Button 3");

    //print what we have selected for the axis controls.
    DKM_DrawString(MOUSE_COL1_X, mouse_line_y(0), mouse_bindings[MA_X_AXIS]);
    DKM_DrawString(MOUSE_COL1_X, mouse_line_y(1), mouse_bindings[MA_Y_AXIS]);
    DKM_DrawString(MOUSE_COL1_X, mouse_line_y(2), mouse_bindings[MA_BUTTON1]);
    DKM_DrawString(MOUSE_COL1_X, mouse_line_y(3), mouse_bindings[MA_BUTTON2]);
    DKM_DrawString(MOUSE_COL1_X, mouse_line_y(4), mouse_bindings[MA_BUTTON3]);

    //check if we have a selection
    if (selection != MA_NONE) {
        //we have an action selected.
        DKM_DrawString(MOUSE_ACTION_LEFT, MOUSE_ACTION_TOP - 10, "Select a command, delete to clear.");

        //check if we have the y axis action selected.
        if (selection == MA_Y_AXIS) {
            if (m_pitch->value < 0) {
                DKM_DrawString(MOUSE_ACTION_LEFT + 8, MOUSE_ACTION_BOTTOM + 10 + 3, "X  Reverse Axis");
            }
            else {
                DKM_DrawString(MOUSE_ACTION_LEFT + 8, MOUSE_ACTION_BOTTOM + 10 + 3, "   Reverse Axis");
            }
        }

        //draw the strings in the command list.
        if (selection == MA_X_AXIS) {
            DKM_DrawString(MOUSE_COMMAND_LEFT + 10, mouse_line_y(0), "Turn L/R");
            DKM_DrawString(MOUSE_COMMAND_LEFT + 10, mouse_line_y(1), "Strafe L/R");
        }
        else if (selection == MA_Y_AXIS) {
            DKM_DrawString(MOUSE_COMMAND_LEFT + 10, mouse_line_y(0), "Look U/D");
            DKM_DrawString(MOUSE_COMMAND_LEFT + 10, mouse_line_y(1), "Move F/B");
        }
        else {
            //draw as many commands as will fit.
            for (int i = 0; i < MOUSE_NUM_COMMANDS_VISIBLE; i++) {
                //get the index of the command we will display.
                int index = i + top_index;
                if (index >= KCC_NUM_COMMANDS) break;

                //print the string.
                DKM_DrawString(MOUSE_COMMAND_LEFT + 10, mouse_line_y(i), bindings[index]->command_name);
            }
        }

        //if either of the axis actions are selected, show the sensitivity slider.
        if (selection == MA_Y_AXIS || selection == MA_X_AXIS) {
            DKM_DrawString(MOUSE_SENSITIVITY_X, MOUSE_SENSITIVITY_Y - 15, "Sensitivity");
        }

        //check if we need to draw the text on the up/down command list buttons.
        if (show_up_button == true) {
//            DKM_DrawString(MOUSE_COMMAND_UP_X + 20, MOUSE_COMMAND_UP_Y + 5, "Up");
        }
        if (show_down_button == true) {
//            DKM_DrawString(MOUSE_COMMAND_DOWN_X + 10, MOUSE_COMMAND_DOWN_Y + 5, "Down");
        }
    }
    //check if we have a hilight
    else {
        DKM_DrawString(MOUSE_ACTION_LEFT, MOUSE_ACTION_TOP - 10, "Select an action.");
    }
}

void CMenuSubMouse::PlaceEntities() {
//    action_box.PlaceEntities();

    //check if we have a selection.
    if (selection != MA_NONE) {
        //we have a selection.
        PositionActionHilight(selection);
        action_hilight_box.PlaceEntities();

        //the box around the command list.
        command_box.PlaceEntities();

        //check if have a hilight in the command box.
        if (command_hilight != -1) {
            PositionCommandHilight(command_hilight);
            command_hilight_box.PlaceEntities();
        }

        //check if we have the y axis action selected.
        if (selection == MA_Y_AXIS) {
            reverse_axis.PlaceEntities();
        }

        //if either of the axis actions are selected, show the sensitivity slider.
        if (selection == MA_Y_AXIS || selection == MA_X_AXIS) {
            sensitivity.PlaceEntities();
        }

        //check if the command list up/down buttons should be drawn.
        if (show_up_button == true) {
            command_up.PlaceEntities();
        }
        if (show_down_button == true) {
            command_down.PlaceEntities();
        }
    }
    //check if we have a hilight
    else if (action_hilight != MA_NONE) {
        PositionActionHilight(action_hilight);
        action_hilight_box.PlaceEntities();
    }

}

bool CMenuSubMouse::Keydown(int key) {
    switch (key) {
        case K_UPARROW: {
            //check if we have an action selected.
            if (selection == MA_NONE) {
                //check if we have a hilight.
                if (action_hilight == MA_NONE) {
                    //hilight the top one.
                    action_hilight = MA_X_AXIS;
                }
                //move the hilight in the action box.
                else if (action_hilight > MA_X_AXIS) {
                    //go up one action.
                    action_hilight = mouse_action(action_hilight - 1);
                }
            }
            else {
                //move the hilight in the command box.
                if (command_hilight <= 0) {
                    //try to scroll the list up.
                    TopIndex(top_index - 1);
                }
                else {
                    //move the hilight up.
                    command_hilight--;
                }
            }
            return true;
        }
        case K_DOWNARROW: {
            //check if we have an action selected.
            if (selection == MA_NONE) {
                //check if we have a hilight.
                if (action_hilight == MA_NONE) {
                    //hilight the top one.
                    action_hilight = MA_X_AXIS;
                }
                //move the hilight in the action box.
                else if (action_hilight < MA_BUTTON3) {
                    //go down one action.
                    action_hilight = mouse_action(action_hilight + 1);
                }
            }
            else {
                if (command_hilight == -1) {
                    //hilight the first one.
                    command_hilight = 0;
                }
                //move the hilight in the command box.
                else if (command_hilight >= MOUSE_NUM_COMMANDS_VISIBLE - 1 || command_hilight >= num_command_items) {
                    //try to scroll the list down.
                    TopIndex(top_index + 1);
                }
                else {
                    //move the hilight down.
                    command_hilight++;
                }
            }
            return true;
        }
        case K_ENTER: {
            //check if we have a selection 
            if (selection == MA_NONE) {
                //check if an action is hilighted.
                if (action_hilight != MA_NONE) {
                    //select the hilighted action.
                    SelectAction(action_hilight);
                    
                    TopIndex(top_index);
                    return true;
                }
            }
            //check if we have a command hilighted.
            else if (command_hilight != -1) {
                //select this command.
                SelectCommand(command_hilight + top_index);

                //unselect our action.
                SelectAction(MA_NONE);
                
                return true;
            }
            break;
        }
        case K_BACKSPACE:
        case K_DEL:
            if (selection != MA_NONE) {
                //clear out the action.
                SelectCommand(-1);

                //deselect our action.
                SelectAction(MA_NONE);
                return true;
            }
            break;
        case K_ESCAPE: {
            //check if we have a selection.
            if (selection == MA_NONE) {
                //leave the help menu.
                menu.Enter();
                return true;
            }

            //get rid of our selection.
            SelectAction(MA_NONE);
            return true;
        }
    }

    return false;
}

bool CMenuSubMouse::MousePos(int x, int y) {
    bool ret = false;
    //check if we have the y axis action selected.
    if (selection == MA_Y_AXIS) {
        if (reverse_axis.MousePos(x, y) == true) ret = true;
        if (sensitivity.MousePos(x, y) == true) ret = true;
    }
    if (selection == MA_X_AXIS) {
        if (sensitivity.MousePos(x, y) == true) ret = true;
    }

    if (selection != MA_NONE) {
        if (show_up_button == true && command_up.MousePos(x, y) == true) ret = true;
        if (show_down_button == true && command_down.MousePos(x, y) == true) ret = true;
    }

    if (ret == false) {
        //check if the mouse position is over one of the actions.
        mouse_action hit = HitAction(x, y);
        if (hit != MA_NONE) {
            //hilight the item that the cursor is on.
            action_hilight = hit;
            return true;
        }

        //check if it is over a command.
        if (selection != MA_NONE) {
            int command = HitCommand(x, y);

            //set the command hilight if the cursor was on a command.
            if (command != -1) {
                command_hilight = command - top_index;
            }
        }
    }

    return ret;
}

bool CMenuSubMouse::MouseDown(int x, int y) {
    if (selection == MA_Y_AXIS) {
        if (reverse_axis.MouseDown(x, y) == true) return true;
        if (sensitivity.MouseDown(x, y) == true) return true;
    }
    if (selection == MA_X_AXIS) {
        if (sensitivity.MouseDown(x, y) == true) return true;
    }

    if (selection != MA_NONE) {
        if (show_up_button == true && command_up.MouseDown(x, y) == true) return true;
        if (show_down_button == true && command_down.MouseDown(x, y) == true) return true;
    }

    //check if they clicked on one of the actions.
    mouse_action clicked = HitAction(x, y);
    if (clicked != MA_NONE) {
        //they clicked on an action.
        SelectAction(clicked);

        TopIndex(top_index);
        return true;
    }

    //check if they clicked on a command.
    if (selection != MA_NONE) {
        int command = HitCommand(x, y);

        if (command != -1) {
            //we clicked an item.
            SelectCommand(command);
            return true;
        }
    }

    return false;
}

#define sign(val) (((val) < 0) ? -1.0f : 1.0f)

bool CMenuSubMouse::MouseUp(int x, int y) {
    bool ret = false;

    if (selection == MA_Y_AXIS) {
        if (reverse_axis.MouseUp(x, y) == true) {
            //they clicked the reverse axis button.  invert the value of the m_pitch cvar.
            Cvar_SetValue("m_pitch", -m_pitch->value);
            return true;
        }
    }
    if (selection == MA_X_AXIS || selection == MA_Y_AXIS) {
        if (sensitivity.MouseUp(x, y) == true) {
            //they changed the sensitivity.
            //read the value from the slider.
            float value = sensitivity.Position();

            //check which axis is selected.
            if (selection == MA_X_AXIS) {
                //check the function of the x axis.
                if (lookstrafe->value != 0) {
                    //they are strafing.
                    Cvar_SetValue("m_side", sign(m_side->value) * value);
                }
                else {
                    //they are turning
                    Cvar_SetValue("m_yaw", sign(m_yaw->value) * value);
                }
            }
            else {
                //check the function of the y axis.
                if (freelook->value != 0) {
                    //they are looking
                    Cvar_SetValue("m_pitch", sign(m_pitch->value) * value);
                }
                else {
                    //they are moving
                    Cvar_SetValue("m_forward", sign(m_forward->value) * value);
                }
            }

            return true;   
        }
    }

    if (selection != MA_NONE) {
        if (show_up_button == true && command_up.MouseUp(x, y) == true) {
            //the want to scroll up.
            TopIndex(top_index - 1);
            return true;
        }
        if (show_down_button == true && command_down.MouseUp(x, y) == true) {
            //they want to scroll down.
            TopIndex(top_index + 1);
            return true;
        }
    }

    return ret;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Video Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubVideo::CMenuSubVideo() {
    enabled = false;
}

void CMenuSubVideo::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubVideo::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Sound Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubSound::CMenuSubSound() {
    enabled = false;
}

void CMenuSubSound::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubSound::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Save game Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubSavegame::CMenuSubSavegame() {
    enabled = false;
}

void CMenuSubSavegame::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubSavegame::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Load game Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubLoadgame::CMenuSubLoadgame() {
    enabled = false;
}

void CMenuSubLoadgame::DrawForeground() {
    DKM_DrawString(200, 200, "You need help alright.");
}

bool CMenuSubLoadgame::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  Multiplayer Menu
//
///////////////////////////////////////////////////////////////////////////////////

CMenuSubMultiplayer::CMenuSubMultiplayer() {
    enabled = false;
}

void CMenuSubMultiplayer::DrawForeground() {
    DKM_DrawString(200, 200, "Multiplayer.");
}

bool CMenuSubMultiplayer::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////////
//
//  New Game Menu
//
///////////////////////////////////////////////////////////////////////////////////

#define NEWGAME_BUTTON_X 130
#define newgame_button_y(i) (155 + (i) * 21)
#define NEWGAME_BUTTON_W 290
#define NEWGAME_BUTTON_H 15

typedef struct {
    char *display_name;
    char *map_name;
} newgame_start;

newgame_start newgames[NUM_NEWGAMES] = {
    {"Episode 1: The Sewer", "map e1m2\n"},
    {"Episode 1: The Slammer", "map e1m3\n"},
    {"Episode 1: The Zoo", "map e1zoo\n"},
    {"Episode 2: Athens", "map e2m3\n"},
    {"Episode 2: Minos' Palace", "map e2m6\n"},
    {"Episode 2: The Zoo", "map e2zoo\n"},
    {"Episode 3: Gharroth's Castle", "map e3m3\n"},
    {"Episode 3: Wyndrax's Tower", "map e3m4\n"},
    {"Episode 3: The Zoo", "map e3zoo\n"},
    {"Episode 4: Escape From Alcatraz", "map e4m1b\n"},
    {"Episode 4: Mishima's Hideout", "map e4m8\n"},
    {"Episode 4: The Zoo", "map e4zoo\n"},
};

CMenuSubNewgame::CMenuSubNewgame() {
    //position the buttons.
    for (int i = 0; i < NUM_NEWGAMES; i++) {
        start[i].Init(NEWGAME_BUTTON_X, newgame_button_y(i), NEWGAME_BUTTON_W, NEWGAME_BUTTON_H, "/models/interface/unitbox/minixl.bmp");
    }
}

void CMenuSubNewgame::Enter() {

}

void CMenuSubNewgame::Animate(int elapsed_time) {
    for (int i = 0; i < NUM_NEWGAMES; i++) {
        start[i].Animate(elapsed_time);
    }
}

void CMenuSubNewgame::DrawForeground() {
    for (int i = 0; i < NUM_NEWGAMES; i++) {
        int len = strlen(newgames[i].display_name);

        DKM_DrawString(NEWGAME_BUTTON_X + NEWGAME_BUTTON_W / 2 - len / 2 * 8, newgame_button_y(i) + 2, newgames[i].display_name);
    }
}

void CMenuSubNewgame::PlaceEntities() {
    for (int i = 0; i < NUM_NEWGAMES; i++) {
        start[i].PlaceEntities();
    }
}

bool CMenuSubNewgame::Keydown(int key) {
    switch (key) {
        case K_ESCAPE:
            //leave the help menu.
            menu.Enter();
            return true;
    }

    return false;
}

bool CMenuSubNewgame::MousePos(int x, int y) {
    bool ret = false;
    //check each button.
    for (int i = 0; i < NUM_NEWGAMES; i++) {
        if (start[i].MousePos(x, y) == true) ret = true;
    }   

    return ret;
}

bool CMenuSubNewgame::MouseDown(int x, int y) {

	//	Nelno:	disable new map commands if in demo mode
#ifdef	DEMO_MODE
	if (_access ("./data/invulnerable.dat", 0) != -1)
	{
		return false;
	}
#endif

    for (int i = 0; i < NUM_NEWGAMES; i++) {
        if (start[i].MouseDown(x, y) == true) return true;
    }

    return false;
}

bool CMenuSubNewgame::MouseUp(int x, int y) {
    for (int i = 0; i < NUM_NEWGAMES; i++) {
        if (start[i].MouseUp(x, y) == true) {
            //they clicked a button.
            menu.ShutdownString("deathmatch 0\ncoop 0\nloading\nkillserver\nwait\nwait\nwait\nwait\nwait\nwait\n%s\n", newgames[i].map_name);
            menu.Shutdown();
            return true;
        }
    }

    return false;
}


