/*

Copyright (C) 2015-2018 Night Dive Studios, LLC.

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 3 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, see <http://www.gnu.org/licenses/>.

*/
/*
 * $Source: r:/prj/cit/src/RCS/mfdfunc.c $
 * $Revision: 1.237 $
 * $Author: mahk $
 * $Date: 1994/11/23 20:34:20 $
 *
 */

// Source code for all MFD Expose/Handler function pairs
// This file is for callbacks only, actual infrastructure belongs
// in newmfd.c

#include <string.h>

#include "ammomfd.h"
#include "bark.h"
#include "biohelp.h"
#include "cardmfd.h"
#include "cybermfd.h"
#include "email.h"
#include "fixtrmfd.h"
#include "gearmfd.h"
#include "invent.h"
#include "mfdgump.h"
#include "mfdpanel.h"
#include "newmfd.h"
#include "objprop.h" // temp
#include "plotware.h"
#include "tools.h"
#include "view360.h"
#include "viewhelp.h"
#include "colors.h"
#include "mainloop.h"
#include "gameloop.h"
#include "mfdart.h"
#include "gamescr.h"
#include "objwarez.h"
#include "objsim.h"
#include "gamestrn.h"
#include "cybstrng.h"
#include "mfdint.h"
#include "mfdext.h"
#include "mfddims.h"
#include "player.h"
#include "wares.h"
#include "drugs.h"
#include "weapons.h"
#include "automap.h"
#include "target.h"
#include "criterr.h"
#include "mfdgadg.h"
#include "objclass.h"
#include "otrip.h"
#include "citres.h"
#include "objuse.h"
#include "sfxlist.h"
#include "musicai.h"
#include "fullscrn.h"
#include "objbit.h"
#include "limits.h"
#include "mapflags.h"
#include "input.h"
#include "gr2ss.h"
#include "mfdfunc.h"
#include "mfdgames.h"
#include "shodan.h"

#define MFD_SHIELD_FUNC 19

// ------------
// Useful Defines
// ------------

#define OVERLOAD_BUTTON_Y (MFD_VIEW_HGT - 24)
#define TEMPR_X 47
#define TEMPR_Y 27
#define TEMPR_WIDTH 13
#define TEMPR_HEIGHT 16
#define TEMPR_DIV 8
#define SETTING_TEXT 46
#define ENERGY_TEXT_LEN 40

static uchar in_or_out = FALSE;

#define LNAME_BUFSIZE 128

#define GOOD_RED (RED_BASE + 5)
#define ITEM_COLOR (0x5A)
#define SELECTED_ITEM_COLOR (0x4C)
#define UNAVAILABLE_ITEM_COLOR (0x60)
#define X_MARGIN 1
#define Y_STEP 5

#define PUSH_CANVAS(x) gr_push_canvas(x)
#define POP_CANVAS() gr_pop_canvas()

#define MFD_REGION(m) ((full_game_3d) ? &(m)->reg2 : &(m)->reg)

// -------
// Globals
// -------

// Forward declaration of array at bottom of file

extern uchar full_game_3d;

LGRegion *mfd_regions[NUM_MFDS];

// ----------------
// Local Prototypes
// ----------------

void mfd_clear_view(void);
void draw_blank_mfd(void);

errtype mfd_item_init(MFD_Func *mfd);
void mfd_expose_blank(MFD *m, ubyte control);
void mfd_item_expose(MFD *m, ubyte control);
uchar mfd_item_handler(MFD *m, uiEvent *e);
void mfd_item_micro_hires_expose(uchar full, int triple);

void mfd_general_inv_expose(MFD *m, ubyte control, ObjID id, uchar full);
uchar mfd_general_inv_handler(MFD *m, uiEvent *ev, int row);

uchar mfd_lantern_button_handler(MFD *m, LGPoint bttn, uiEvent *ev, void *data);
void mfd_lantern_setting(int setting);
errtype mfd_lanternware_init(MFD_Func *f);
void mfd_lanternware_expose(MFD *mfd, ubyte control);

void draw_ammo_button(int triple, short x, short y);

errtype mfd_anim_init();
void mfd_anim_expose(MFD *m, ubyte control);

errtype mfd_weapon_init(MFD_Func *mfd);
void mfd_weapon_expose(MFD *m, ubyte control);
uchar mfd_weapon_handler(MFD *m, uiEvent *e);
uchar mfd_weapon_beam_handler(MFD *m, uiEvent *e);
uchar mfd_weapon_projectile_handler(MFD *m, uiEvent *e, weapon_slot *ws);
uchar mfd_weapon_expose_projectile(MFD *m, weapon_slot *ws, ubyte control);
void mfd_weapon_expose_beam(weapon_slot *ws, ubyte id, uchar Redraw);
void mfd_weapon_draw_temp(ubyte temp);
void mfd_weapon_draw_ammo_buttons(int num_ammo_buttons, int ammo_subclass, ubyte *ammo_types, ubyte curr_ammo_type,
                                  int ammo_count);
void mfd_weapon_draw_beam_status_bar(int charge, int setting, uchar does_overload);


uchar weapon_mfd_temp;

void mfd_bioware_expose(MFD *m, ubyte control);

// ------------
// USEFUL STUFF
// ------------

void mfd_clear_view(void) {
    if (full_game_3d)
        return;
    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    ss_bitmap(&mfd_background, 0, 0);
    // gr_bitmap(&mfd_background, 0, 0);
    mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
}

int mfd_bmap_id(int triple) {
    int obclass = TRIP2CL(triple);
    int t = CPTRIP(triple);
    return MKREF(RES_mfdClass_1 + obclass, t);
}

void draw_blank_mfd(void) {
    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
    // draw_hires_resource_bm(REF_IMG_bmBlankMFD, 0, 0);
    draw_res_bm(MKREF(RES_mfdArtOverlays, MFD_ART_TRIOP), 0, 0);
}

// --------------------
// FUNCTION INITIALIZER
// --------------------

// ---------------------------------------------------------------------------
// mfd_init_funcs()
//
// Here is where you set the global MFD_Func structures to point at
// expose/handler pairs, and also where you set their flags.  This is also
// where MFD virtual slots are set to point at their functions.

void mfd_init_funcs() {
    int i;
    // Define a couple of MFD functions.
    for (i = 0; i < MFD_NUM_FUNCS; i++)
        if (mfd_funcs[i].init != NULL) {
            errtype err = mfd_funcs[i].init(&mfd_funcs[i]);
            if (err != OK)
                critical_error(CRITERR_MISC | 1);
        }

    // Set slots to point at functions
    set_slot_to_func(MFD_WEAPON_SLOT, MFD_WEAPON_FUNC, MFD_ACTIVE);
    set_slot_to_func(MFD_ITEM_SLOT, MFD_ITEM_FUNC, MFD_ACTIVE);
    set_slot_to_func(MFD_MAP_SLOT, MFD_MAP_FUNC, MFD_ACTIVE);
    set_slot_to_func(MFD_INFO_SLOT, MFD_EMPTY_FUNC, MFD_ACTIVE);
    set_slot_to_func(MFD_TARGET_SLOT, MFD_TARGET_FUNC, MFD_ACTIVE);

    //   set_slot_to_func(MFD_SPECIAL_SLOT, MFD_ANIM_FUNC,   MFD_FLASH);
    //   set_slot_to_func(MFD_SPECIAL_SLOT, MFD_EMPTY_FUNC, MFD_EMPTY);

    //   debug_mfd_func_table();
    //   debug_mfd_slots_table();
}

// ===========================================================================
//
// ===========================================================================

//                             --------------------
//                             ACTUAL MFD FUNCTIONS
//                             --------------------

// ===========================================================================
//                             * THE WEAPON MFD *
// ===========================================================================

// Hey, wow, floats and doubles are UNcool.

#define mfd_pixels_per_charge_unit (FIX_UNIT * 69 / 100) // How many hor. pixels equals a % of charge?
#define mfd_charge_units_per_pixel (FIX_UNIT * 100 / 69)

// note _LEFT is 0, _RIGHT is 1
#define MFDLeftOffs MFD_LEFT
#define MFDRightOffs MFD_RIGHT
#define MFDLastWeapon 0
#define MFDAmmo 2
#define MFDLastBeamHeat 4

#define MFD_Access(which, lorr) mfd_fdata[MFD_WEAPON_FUNC][(which) + (lorr)]

#define MFDGetLastLeftWeapon mfd_fdata[MFD_WEAPON_FUNC][0]
#define MFDGetLastRightWeapon mfd_fdata[MFD_WEAPON_FUNC][1]
#define MFDGetLeftAmmo mfd_fdata[MFD_WEAPON_FUNC][2]
#define MFDGetRightAmmo mfd_fdata[MFD_WEAPON_FUNC][3]
#define MFDGetLastLeftBeamHeat mfd_fdata[MFD_WEAPON_FUNC][4]
#define MFDGetLastRightBeamHeat mfd_fdata[MFD_WEAPON_FUNC][5]
#define MFDSetLastLeftWeapon(n) (mfd_fdata[MFD_WEAPON_FUNC][0] = (n))
#define MFDSetLastRightWeapon(n) (mfd_fdata[MFD_WEAPON_FUNC][1] = (n))
#define MFDSetLeftAmmo(n) (mfd_fdata[MFD_WEAPON_FUNC][2] = (n))
#define MFDSetRightAmmo(n) (mfd_fdata[MFD_WEAPON_FUNC][3] = (n))
#define MFDSetLastLeftBeamHeat(n) (mfd_fdata[MFD_WEAPON_FUNC][4] = (n))
#define MFDSetLastRightBeamHeat(n) (mfd_fdata[MFD_WEAPON_FUNC][5] = (n))

#define MFD_BEAMWPN_STAT_BORDER GREEN_YELLOW_BASE
#define MFD_BEAMWPN_STAT_CHARGE WHITE
#define MFD_BEAMWPN_STAT_MAXCHARGE PURPLE_BASE
#define MFD_BEAMWPN_STAT_DEADSPACE BLACK

#define WEAPON_ART_Y 7

#define AMMO_BUTTON_H 24
#define AMMO_BUTTON_W 23
#define AMMO_BUTTON_Y (MFD_VIEW_HGT - AMMO_BUTTON_H)
#define AMMO_STRING_Y (AMMO_BUTTON_Y - 4)
#define AMMO_NAME_Y (MFD_VIEW_HGT - 6)

#define AMMO_BUTTON_X1 29
#define AMMO_BUTTON_DX1 0
#define AMMO_BUTTON_X2 11
#define AMMO_BUTTON_DX2 31
#define AMMO_BUTTON_X3 1
#define AMMO_BUTTON_DX3 24

#define MFD_BEAM_RECT_X1 1
#define MFD_BEAM_RECT_X2 71
#define MFD_BEAM_RECT_Y1 52
#define MFD_BEAM_RECT_Y2 56

LGRect MfdAmmoRectZone = {{0, AMMO_BUTTON_Y}, {MFD_VIEW_WID, AMMO_BUTTON_Y + AMMO_BUTTON_H}};
LGRect MfdBeamStatusRect;

#define NO_CONSTRAIN NUM_MFDS
static ubyte beam_constrain = NO_CONSTRAIN;

LGCursor slider_cursor;
grs_bitmap slider_cursor_bmap;

// ---------- WEAPON MFD FUNC ---------------

// --------------------------------------------------------------------------
// mfd_weapon_init()
//
// Initializes the MFD weapons function.

errtype mfd_weapon_init(MFD_Func *mfd) {
#ifndef NO_DUMMIES
    void *yum;
    yum = mfd;
#endif // NO_DUMMIES

    MFDSetLastLeftWeapon(0);
    MFDSetLastRightWeapon(0);
    MFDSetLeftAmmo(0xFF);
    MFDSetRightAmmo(0xFF);

    MfdBeamStatusRect.ul.x = MFD_BEAM_RECT_X1;
    MfdBeamStatusRect.ul.y = MFD_BEAM_RECT_Y1;
    MfdBeamStatusRect.lr.x = MFD_BEAM_RECT_X2;
    MfdBeamStatusRect.lr.y = MFD_BEAM_RECT_Y2;

    return OK;
}

// --------------------------------------------------------------------------
// mfd_weapon_expose()
//
// Draws an overlay of a weapon in the current mfd slot.

void mfd_weapon_expose(MFD *m, ubyte control) {
    weapon_slot *ws;
    char buf[50];
    int triple;
    uchar punt = player_struct.actives[ACTIVE_WEAPON] == EMPTY_WEAPON_SLOT;
    uchar Redraw = FALSE;
    uchar RedrawAmmoArea = TRUE;
    extern uchar full_game_3d;

    if (control == 0) {
        uiCursorStack *cs;
        weapon_mfd_temp = FALSE;

        uiGetRegionCursorStack(MFD_REGION(m), &cs);
        uiPopCursorEvery(cs, &slider_cursor);

        if (beam_constrain == m->id) {
            beam_constrain = NO_CONSTRAIN;
            // KLC         mouse_unconstrain();
        }
        return;
    }

    // Get the triple for the current weapon

    if (!punt)
        ws = &player_struct.weapons[player_struct.actives[ACTIVE_WEAPON]];
    if (ws->type == EMPTY_WEAPON_SLOT)
        punt = TRUE;
    if (punt) {
        mfd_expose_blank(m, control);
        return;
    }

    triple = MAKETRIP(CLASS_GUN, ws->type, ws->subtype);

    if (control & MFD_EXPOSE) {

        PUSH_CANVAS(pmfd_canvas);
        mfd_clear_rects();

        if (control & MFD_EXPOSE_FULL)
            Redraw = TRUE;
        if (MFD_Access(m->id, MFDLastWeapon) != player_struct.actives[ACTIVE_WEAPON]) {
            Redraw = TRUE;
            MFD_Access(m->id, MFDLastWeapon) = player_struct.actives[ACTIVE_WEAPON];
            MFD_Access(m->id, MFDAmmo) = 0xFF;
            weapon_mfd_temp = FALSE;
        }

        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        if (!full_game_3d)
            ss_bitmap(&mfd_background, 0, 0);
        // gr_bitmap(&mfd_background, 0, 0);

        // Draw the appropriate weapon art.
        // We draw it here because it is effectively "background"
        // Hopefully update-rects will take care of us..
        {
            int id = mfd_bmap_id(triple);
            draw_res_bm(id, (MFD_VIEW_WID - res_bm_width(id)) / 2, WEAPON_ART_Y);
            // draw_hires_resource_bm(id,
            //		(SCONV_X(MFD_VIEW_WID)-res_bm_width(id))/2, SCONV_Y(WEAPON_ART_Y));  // is this right?
        }

        // This is all stuff that should be drawn for a full expose of
        // a new weapon
        if (Redraw) {
            short y = 2;
            short w, h;

            // Print name of gun in top line of mfd
            get_weapon_long_name(ws->type, ws->subtype, buf);
            mfd_draw_string(buf, X_MARGIN, y, GOOD_RED, TRUE);
            gr_string_size(buf, &w, &h);
            y += h;

            mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        }

        // Here is where the dynamic info for a given weapon is drawn; ie; data that
        // requires a redraw even when the current weapon has not changed

        if ((ws->type == GUN_SUBCLASS_BEAM) || (ws->type == GUN_SUBCLASS_BEAMPROJ))
            mfd_weapon_expose_beam(ws, m->id, Redraw);
        else if (ws->type != GUN_SUBCLASS_HANDTOHAND)
            RedrawAmmoArea = mfd_weapon_expose_projectile(m, ws, control);

        // Redraw everything
        POP_CANVAS();
        mfd_update_rects(m);
    }

}

// --------------------------------------------------------------------------
// mfd_weapon_expose_projectile()
//
// Exposes relevant weapons mfd info for a projectile weapon.
// returns whether or not it drew the ammo buttons

uchar mfd_weapon_expose_projectile(MFD *m, weapon_slot *ws, ubyte control) {
    int num_ammo_buttons;
    int ammo_subclass;
    ubyte ammo_types[3];
    uchar RedrawAmmoFlag = FALSE;

    // Get the ammo data for the current weapon
    get_available_ammo_type(ws->type, ws->subtype, &num_ammo_buttons, ammo_types, &ammo_subclass);

    /*
     * FIXME: shamaz 20.04.2020
     *
     * The following two lines were present in the code provided by
     * Nightdive Studios, LLC along with the following comment:
     *    > ammo_type and setting have same memory location in
     *    > weapon_slot union. setting can have values greater than
     *    > num ammo buttons so check for and fix oob
     * It's not clear how oob access can happen because .setting and
     * .ammo_type fields are used independently (based on a type of
     * the weapon, e.g. beam or other range weapon) and never .setting
     * value is treated as .ammo_type and vice versa.
     *
     * On the other hand they cause a problem when you reload your
     * weapon with the last clip (of any suitable type). At first, if
     * you unload this clip, it get lost (disappears from your
     * inventory forever) even if not depleted completely. At second,
     * this final clip is replaced with a clip of other type
     * (sometimes even inappropriate for this particular weapon).
     *
     * So I find it better to comment these lines out. Seems to be
     * perfectly safe, but some more testing is required.
     */
    /* if (ws->ammo_type >= num_ammo_buttons) */
    /*     ws->ammo_type = (num_ammo_buttons ? ammo_types[0] : 0); */

    if ((control & MFD_EXPOSE_FULL) || (ws->ammo == 0))
        RedrawAmmoFlag = TRUE;
    else if (MFD_Access(m->id, MFDAmmo) != ws->ammo) {
        RedrawAmmoFlag = TRUE;
        MFD_Access(m->id, MFDAmmo) = ws->ammo;
    }

    // Redraw ammo buttons if neccessary
    if (RedrawAmmoFlag)
        mfd_weapon_draw_ammo_buttons(num_ammo_buttons, ammo_subclass, ammo_types, ws->ammo_type, ws->ammo);

    return RedrawAmmoFlag;
}

// ---------------------------------------------------------------------------
// mfd_weapon_draw_ammo_buttons()
//
// Draws the labelled buttons of ammo on a projectile gun's mfd

#define MAX_CART_COLORS 3
#define CARTRIDGE_BRACKET 8 // cartridges per color
static uchar cart_colors[MAX_CART_COLORS] = {GOOD_RED, 0x4b, GREEN_BASE + 2};

void draw_ammo_button(int triple, short x, short y) {
    short h;
    int id;
    int carts = player_struct.cartridges[CPTRIP(triple)];
    ubyte cnum = lg_min(MAX_CART_COLORS - 1, (carts - 1) / CARTRIDGE_BRACKET);

    // Draw the outline of an ammo box
    draw_res_bm(REF_IMG_BullFrame + lg_min(2, lg_max(3 - carts, 0)), x, y);
    id = mfd_bmap_id(triple);
    h = res_bm_height(id);
    draw_res_bm(id, x + 4, y + AMMO_BUTTON_H - 4 - h);
    // draw_hires_resource_bm(id, SCONV_X(x+4), SCONV_Y(y+AMMO_BUTTON_H-3)-h);
    if (carts > 0) {
        int cnt = carts % CARTRIDGE_BRACKET;
        if (cnt == 0)
            cnt = CARTRIDGE_BRACKET;
        gr_set_fcolor(cart_colors[cnum]);
        while (cnt-- > 0) {
            short cy = y + AMMO_BUTTON_H - 5 - 2 * cnt;
            ss_hline(x + AMMO_BUTTON_W - 6, cy, x + AMMO_BUTTON_W - 5);
        }
    }
    if (carts > 0 || player_struct.partial_clip[CPTRIP(triple)] > 0)
        mfd_add_rect(x, y, x + AMMO_BUTTON_W, y + AMMO_BUTTON_H);
}

void mfd_weapon_draw_ammo_buttons(int num_ammo_buttons, int ammo_subclass, ubyte *ammo_types, ubyte curr_ammo_type,
                                  int ammo_count) {
    int i, triple;

    // Draw ammo boxes
    if (ammo_count == 0) {
        for (i = 0; i < num_ammo_buttons; i++) {
            short x = (ammo_types[i]) * AMMO_BUTTON_W;
            triple = MAKETRIP(CLASS_AMMO, ammo_subclass, ammo_types[i]);
            // Get useful ammo information for box label
            draw_ammo_button(triple, x, AMMO_BUTTON_Y);
        }
        if (num_ammo_buttons > 0)
            mfd_draw_string(get_temp_string(REF_STR_ClickToLoad), 1, AMMO_STRING_Y, GREEN_YELLOW_BASE, TRUE);
    } else {
        char buf[4], buf2[50];
        triple = MAKETRIP(CLASS_AMMO, ammo_subclass, curr_ammo_type);

        sprintf(buf, "%d", ammo_count);
        gr_set_font(ResGet(RES_mediumLEDFont));
        mfd_string_shadow = MFD_SHADOW_NEVER;
        mfd_draw_font_string(buf, MFD_VIEW_WID - gr_string_width(buf) - 2, AMMO_BUTTON_Y + 2, GOOD_RED,
                             RES_mediumLEDFont, TRUE);
        mfd_string_shadow = MFD_SHADOW_FULLSCREEN; // default

        get_object_short_name(triple, buf2, 50);
        gr_set_font(ResGet(MFD_FONT));
        mfd_draw_string(buf2, MFD_VIEW_WID - gr_string_width(buf2) - 2, AMMO_NAME_Y, GREEN_YELLOW_BASE, TRUE);

        draw_ammo_button(triple, 0, AMMO_BUTTON_Y);
        draw_res_bm(REF_IMG_BullRightArrow, AMMO_BUTTON_W, AMMO_BUTTON_Y);
    }

    if ((num_ammo_buttons == 0) && (ammo_count == 0)) {
        draw_res_bm(REF_IMG_NoAmmo, (MFD_VIEW_WID - res_bm_width(REF_IMG_NoAmmo)) / 2, AMMO_BUTTON_Y);
    }
    mfd_add_rect(0, AMMO_STRING_Y, MFD_VIEW_WID, MFD_VIEW_HGT);
    return;
}

#define MFD_BEAM_WARM 0x40
#define MFD_BEAM_HOT 0x35
#define MFD_BEAM_READY 0x58

ubyte temp_levels[TEMPR_WIDTH] = {1, 2, 2, 3, 3, 4, 5, 6, 7, 9, 11, 13, 16};

// --------------------------------------------------------------------------
// mfd_weapon_draw_temp()
//

void mfd_weapon_draw_temp(ubyte temp) {
    int i;

    gr_set_fcolor(MFD_BEAM_READY);

    for (i = 0; i < TEMPR_WIDTH; i++) {
        if (i == 5)
            gr_set_fcolor(MFD_BEAM_WARM);
        else if (i == 10)
            gr_set_fcolor(MFD_BEAM_HOT);

        if (temp >= i * TEMPR_DIV)
            ss_vline(TEMPR_X + i * 2, TEMPR_Y + TEMPR_HEIGHT - temp_levels[i], TEMPR_Y + TEMPR_HEIGHT);
    }
}

// --------------------------------------------------------------------------
// mfd_weapon_draw_beam_status_bar()
//
// Takes the current charge and the maximum charge, and draws the dynamic
// portion of the beam weapon status bar

void mfd_weapon_draw_beam_status_bar(int amt, int setting, uchar does_overload) {
    ubyte setting_x;

    setting = (BEAM_SETTING_VAL(setting) < MIN_ENERGY_USE) ? 1 : (BEAM_SETTING_VAL(setting) - MIN_ENERGY_USE);
    if (does_overload)
        setting <<= 1;

    setting++;

    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);

    gr_set_fcolor(MFD_BEAMWPN_STAT_BORDER); // OUTLINE
    ss_box(MfdBeamStatusRect.ul.x - 1, MfdBeamStatusRect.ul.y - 1, MfdBeamStatusRect.lr.x + 1,
           MfdBeamStatusRect.lr.y + 1);

    setting_x = (ubyte)fix_int(setting * mfd_pixels_per_charge_unit);
    setting_x = lg_min(MfdBeamStatusRect.lr.x - MfdBeamStatusRect.ul.x, setting_x);

    // draw the settings bar only if we're not in overload mode

    if (!in_or_out)
        draw_raw_resource_bm(REF_IMG_BeamSetting, MfdBeamStatusRect.ul.x + setting_x - 3, MfdBeamStatusRect.ul.y - 1);

    mfd_add_rect(MfdBeamStatusRect.ul.x, MfdBeamStatusRect.ul.y, MfdBeamStatusRect.lr.x, MfdBeamStatusRect.lr.y);
    return;
}

// --------------------------------------------------------------------------
// mfd_weapon_expose_beam()
//
// Exposes relevant weapons mfd info for a beam weapon.

void mfd_weapon_expose_beam(weapon_slot *ws, ubyte id, uchar Redraw) {
    char buf[ENERGY_TEXT_LEN];
    uchar does_overload = FALSE;

    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    if (id == MFD_LEFT) {
        if (MFDGetLastLeftBeamHeat > ws->ammo)
            Redraw = TRUE;
        MFDSetLastLeftBeamHeat(ws->ammo);
    }
    if (id == MFD_RIGHT) {
        if (MFDGetLastRightBeamHeat > ws->ammo)
            Redraw = TRUE;
        MFDSetLastRightBeamHeat(ws->ammo);
    }

    does_overload = does_weapon_overload(ws->type, ws->subtype);
    if (does_overload) {
        // if beam weapon is not set to overload then draw overload button, otherwise say "overload enabled"
        if (Redraw && OVERLOAD_VALUE(ws->setting)) {
            short w = res_bm_width(REF_IMG_BeamOverloadOn);

            draw_raw_resource_bm(REF_IMG_BeamOverloadOn, 1, OVERLOAD_BUTTON_Y);
            mfd_add_rect(1, OVERLOAD_BUTTON_Y, 1 + w, MFD_VIEW_HGT);

            get_string(REF_STR_Overload, buf, ENERGY_TEXT_LEN);
            mfd_draw_string(buf, 1, SETTING_TEXT, MFD_BEAM_HOT, TRUE);
        } else {
            short w = res_bm_width(REF_IMG_BeamOverload);

            if (Redraw) {
                (ws->ammo < MINIMUM_OVERLOAD) ? draw_raw_resource_bm(REF_IMG_BeamOverload, 1, OVERLOAD_BUTTON_Y)
                                              : draw_raw_resource_bm(REF_IMG_BeamOverloadOff, 1, OVERLOAD_BUTTON_Y);
                mfd_add_rect(1, OVERLOAD_BUTTON_Y, 1 + w, MFD_VIEW_HGT);
                get_string(REF_STR_EnergySetting, buf, ENERGY_TEXT_LEN);
                mfd_draw_string(buf, 1, SETTING_TEXT, MFD_BEAM_READY, TRUE);
            }
        }
    }

    get_string(REF_STR_LowSetting, buf, ENERGY_TEXT_LEN);
    mfd_draw_string(buf, 2, MFD_BEAM_RECT_Y1 - 1, MFD_BEAM_READY, TRUE);
    get_string(REF_STR_HighSetting, buf, ENERGY_TEXT_LEN);
    mfd_draw_string(buf, 57, MFD_BEAM_RECT_Y1 - 1, MFD_BEAM_READY, TRUE);

    draw_raw_resource_bm(REF_IMG_BeamTemperature, TEMPR_X, TEMPR_Y);

    mfd_weapon_draw_temp(ws->ammo);

    // Redraw the beam energy status bar
    mfd_weapon_draw_beam_status_bar(ws->ammo, ws->setting, does_overload);

    return;
}

// ---------------------------------------------------------------------------
// mfd_weapon_handler()
//
// Mostly responsible for figuring out which ammo boxes were clicked on
// to select new ammo, and for manipulating the charge on beam weapons

uchar mfd_weapon_handler(MFD *m, uiEvent *e) {
    weapon_slot *ws;
    int triple;

    // Get the triple for the current weapon
    ws = &player_struct.weapons[player_struct.actives[ACTIVE_WEAPON]];
    triple = MAKETRIP(CLASS_GUN, ws->type, ws->subtype);

    switch (ws->type) {
    case GUN_SUBCLASS_BEAM:     // Is beam charge being set?
    case GUN_SUBCLASS_BEAMPROJ: // Is beam charge being set?
        //         if (!(mouse->action & ~MOUSE_MOTION) && !(mouse->buttons & (1 << MOUSE_LBUTTON)))
        //            return FALSE;
        return mfd_weapon_beam_handler(m, e);
        break;
    case GUN_SUBCLASS_PISTOL:
    case GUN_SUBCLASS_AUTO:
    case GUN_SUBCLASS_SPECIAL:
        if (e->mouse_data.action == MOUSE_MOTION)
            return FALSE;
        return mfd_weapon_projectile_handler(m, e, ws);
        break;
    default:
        break;
    }
    return FALSE;
}

ubyte old_energy_setting = 0xFF;

// ---------------------------------------------------------------------------
// mfd_weapon_beam_handler()
//
// This is the handler for beam type weapons in the weapons mfd.

uchar mfd_weapon_beam_handler(MFD *m, uiEvent *e) {
    uchar retval = TRUE;
    LGRect r;
    ubyte setting, setting_x;
    weapon_slot *ws = &player_struct.weapons[player_struct.actives[ACTIVE_WEAPON]];
    uchar overld = does_weapon_overload(ws->type, ws->subtype);

#ifdef CURSOR_BACKUPS
    extern grs_bitmap backup_mfd_cursor;
    extern uchar *backup[NUM_BACKUP_BITS];
#endif

    // We're interested in this event iff its a mouse up,down,action event
    // in the beam status bar.
    if (!(e->mouse_data.action & (MOUSE_LUP | MOUSE_LDOWN | MOUSE_MOTION))) {
        return FALSE;
    }

    if (overld) {
        // okay - here's the overload button code
        if (e->mouse_data.action & MOUSE_LDOWN) {

            if (ws->ammo < MINIMUM_OVERLOAD) {
                // set up the rect for the overload button
                r.ul.x = (MFD_VIEW_WID - res_bm_width(REF_IMG_NoAmmo)) / 2;
                r.ul.y = OVERLOAD_BUTTON_Y;
                r.lr.x = r.ul.x + res_bm_width(REF_IMG_BeamOverload);
                r.lr.y = OVERLOAD_BUTTON_Y + res_bm_height(REF_IMG_BeamOverload);
                RECT_OFFSETTED_RECT(&r, m->rect.ul, &r);

                // check if we clicked in the button
                if (RECT_TEST_PT(&r, e->pos)) {
                    // toggle between the two values
                    chg_set_flg(INVENTORY_UPDATE);
                    OVERLOAD_VALUE(ws->setting) ? OVERLOAD_RESET(ws->setting) : OVERLOAD_SET(ws->setting);
                    mfd_force_update(); // make sure it redraws the mfd
                    return TRUE;
                }
            }
        }
    }

    RECT_OFFSETTED_RECT(&MfdBeamStatusRect, m->rect.ul, &r);

    if (!RECT_TEST_PT(&r, e->pos)) {
        uiCursorStack *cs;

        uiGetRegionCursorStack(MFD_REGION(m), &cs);
        uiPopCursorEvery(cs, &slider_cursor);
        mfd_notify_func(MFD_WEAPON_FUNC, MFD_WEAPON_SLOT, FALSE, MFD_ACTIVE, FALSE);
        if (!in_or_out)
            return retval;
    }
    if (!in_or_out && (e->mouse_data.buttons == 0))
        return retval;

    // If the left button was pushed, we constrain the mouse to
    // the beam status bar, and change the cursor appropriately
    if (e->mouse_data.action & MOUSE_LDOWN) {

        in_or_out = TRUE;

        // Constrain the mouse to a 1-pixel y
        slider_cursor.hotspot.x = slider_cursor_bmap.w / 2;
        slider_cursor.hotspot.y = (slider_cursor_bmap.h / 2) + e->pos.y - (r.ul.y + r.lr.y) / 2;
        ui_mouse_constrain_xy(r.ul.x + 1, e->pos.y, r.lr.x - 2, e->pos.y);
        beam_constrain = m->id;
        // Get our funky mfd-beam-phaser-setting cursor
        uiPushRegionCursor(MFD_REGION(m), &slider_cursor);
#ifdef CURSOR_BACKUPS
        backup[20] = (uchar *)malloc(f->bm.w * f->bm.h);
        LG_memcpy(backup[20], f->bm.bits, f->bm.w * f->bm.h);
        gr_init_bm(&backup_mfd_cursor, backup[14], BMT_FLAT8, 0, mfd_cursor.w, mfd_cursor.h);
#endif
        retval = TRUE;
    }

    // If the left button was released, unconstrain the mouse and reset
    // the cursor bitmap.  There's no else here for the weird case that
    // an up and down event might happen "simultaneously"
    if (e->mouse_data.action & MOUSE_LUP) {

        in_or_out = FALSE;

        // Let the mouse run free
        // note that we are NOT using the UI here since we're already looking at grd_cap
        mouse_constrain_xy(0, 0, grd_cap->w - 1, grd_cap->h - 1);
        beam_constrain = NO_CONSTRAIN;

        uiPopRegionCursor(MFD_REGION(m));
        mfd_notify_func(MFD_WEAPON_FUNC, MFD_WEAPON_SLOT, FALSE, MFD_ACTIVE, TRUE);

        retval = TRUE;
    }

    // Calculate the max charge setting based on mouse position (making goofy
    // exceptions for the endpoints for aesthetics) and set the weapon
    // accordingly.
    setting_x = e->pos.x - r.ul.x;
    if ((e->mouse_data.action & MOUSE_MOTION) && (setting_x == old_energy_setting))
        return retval;
    old_energy_setting = setting_x;

    setting = (ubyte)fix_int(setting_x * mfd_charge_units_per_pixel);

    if (overld)
        setting >>= 1;

    setting += MIN_ENERGY_USE;
    uiSetCursor();

    set_beam_weapon_max_charge(player_struct.actives[ACTIVE_WEAPON], setting);
    //      min(MAX_HEAT,setting));

    return TRUE;
}

// ---------------------------------------------------------------------------
// mfd_weapon_projectile_handler()
//
// This is the handler for projectile weapons in the weapons mfd.

uchar mfd_weapon_projectile_handler(MFD *m, uiEvent *e, weapon_slot *ws) {
    int ammo_subclass, num_ammo_buttons;
    ubyte ammo_types[3];
    LGPoint pos = e->pos;

    pos.x -= m->rect.ul.x;
    pos.y -= m->rect.ul.y;

    if (pos.y < AMMO_BUTTON_Y)
        return FALSE;
    // If we're already loaded, check for double click.
    if (ws->ammo > 0) {
        uchar retval = FALSE;
        if (e->mouse_data.action & UI_MOUSE_LDOUBLE) {
            unload_current_weapon();
            MFDSetLeftAmmo(0xFF);
            MFDSetRightAmmo(0xFF);
            mfd_notify_func(MFD_WEAPON_FUNC, MFD_WEAPON_SLOT, FALSE, MFD_ACTIVE, FALSE);
            mfd_notify_func(MFD_ITEM_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
            retval = TRUE;
        } else if (e->mouse_data.action & MOUSE_LDOWN) {
            string_message_info(REF_STR_DClickToUnload);
            retval = TRUE;
        }
        return retval;
    }

    // If it wasn't a left-mouse-button down event, throw it away.
    if (!(e->mouse_data.action & (MOUSE_LDOWN | UI_MOUSE_LDOUBLE)))
        return FALSE;

    // Get the ammo data for the current weapon
    get_available_ammo_type(ws->type, ws->subtype, &num_ammo_buttons, ammo_types, &ammo_subclass);
    {
        int b = pos.x / AMMO_BUTTON_W;
        int atype;
        for (atype = 0; atype < num_ammo_buttons; atype++) {
            // If we have a hit, we let each mfd know independently that
            // it needs to redraw its ammo buttons, to save redraw time
            if (b == ammo_types[atype] && change_ammo_type(b)) {
                if (weapon_mfd_temp) {
                    int mfd;
                    for (mfd = 0; mfd < NUM_MFDS; mfd++) {
                        if (player_struct.mfd_current_slots[mfd] == MFD_WEAPON_SLOT)
                            restore_mfd_slot(mfd);
                    }
                    weapon_mfd_temp = FALSE;
                }
                MFDSetLeftAmmo(0xFF);
                MFDSetRightAmmo(0xFF);
                mfd_notify_func(MFD_WEAPON_FUNC, MFD_WEAPON_SLOT, FALSE, MFD_ACTIVE, FALSE);
                mfd_notify_func(MFD_ITEM_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
                break;
            }
        }
    }

    return TRUE;
}

void weapon_mfd_for_reload(void) {
    uchar target_pri;
    uchar take_mfd;

    // Do not take down target mfd in favor of weapon in this case!
    target_pri = mfd_funcs[MFD_TARGET_FUNC].priority;
    mfd_funcs[MFD_TARGET_FUNC].priority = 1;
    take_mfd = mfd_choose_func(MFD_WEAPON_FUNC, MFD_WEAPON_SLOT);
    if (player_struct.mfd_current_slots[take_mfd] != MFD_WEAPON_SLOT) {
        save_mfd_slot(take_mfd);
        weapon_mfd_temp = TRUE;
    }
    mfd_change_slot(take_mfd, MFD_WEAPON_SLOT);
    mfd_funcs[MFD_TARGET_FUNC].priority = target_pri;
}

// ===========================================================================
//                             * THE ITEM MFD *
// ===========================================================================

// OK, the item MFD is pretty heinous, and, in fact, they all
// really want to be their own MFDfuncs, so, let's grant them their wish!

#define MFDGetLastItemClass(m) mfd_fdata[MFD_ITEM_FUNC][(m)]
#define MFDGetLastItemType(m) mfd_fdata[MFD_ITEM_FUNC][(m) + 2]
#define MFDGetCurrItemClass(m) mfd_fdata[MFD_ITEM_FUNC][(m) + 4]
#define MFDGetCurrItemType(m) mfd_fdata[MFD_ITEM_FUNC][(m) + 6]

#define MFDSetLastItemClass(m, n) mfd_fdata[MFD_ITEM_FUNC][(m)] = (n)
#define MFDSetLastItemType(m, n) mfd_fdata[MFD_ITEM_FUNC][(m) + 2] = (n)
#define MFDSetCurrItemClass(m, n) mfd_fdata[MFD_ITEM_FUNC][(m) + 4] = (n)
#define MFDSetCurrItemType(m, n) mfd_fdata[MFD_ITEM_FUNC][(m) + 6] = (n)

LGRect MfdGrenadeBox[2];

#define MFD_GRENADE_BOX_X1 27
#define MFD_GRENADE_BOX_X2 47
#define MFD_GRENADE_BOX_Y 5
#define MFD_GRENADE_BOX_W 5
#define MFD_GRENADE_BOX_H 5

#define HARDWARE_BUTTON_H 13
#define HARDWARE_BUTTON_W 44
#define HARDWARE_BUTTON_X ((MFD_VIEW_WID - HARDWARE_BUTTON_W) / 2)
#define HARDWARE_BUTTON_Y (MFD_VIEW_HGT - HARDWARE_BUTTON_H - 1)

#define DRUG_BUTTON_H 13
#define DRUG_BUTTON_W 32
#define DRUG_BUTTON_X ((MFD_VIEW_WID - DRUG_BUTTON_W) / 2)
#define DRUG_BUTTON_Y (MFD_VIEW_HGT - 1 - DRUG_BUTTON_H)

// --------------------------------------------------------------------------
// mfd_item_init()
//
// Sets some info.

errtype mfd_item_init(MFD_Func *mfd) {
    MFDSetCurrItemClass(0, MFD_INV_NULL);
    MFDSetCurrItemClass(1, MFD_INV_NULL);
    MFDSetLastItemClass(0, MFD_INV_NULL);
    MFDSetLastItemClass(1, MFD_INV_NULL);

    MfdGrenadeBox[0].ul.x = MFD_GRENADE_BOX_X1;
    MfdGrenadeBox[0].ul.y = MFD_GRENADE_BOX_Y;
    MfdGrenadeBox[0].lr.x = MFD_GRENADE_BOX_X1 + MFD_GRENADE_BOX_W;
    MfdGrenadeBox[0].lr.y = MFD_GRENADE_BOX_Y + MFD_GRENADE_BOX_H;

    MfdGrenadeBox[1].ul.x = MFD_GRENADE_BOX_X2;
    MfdGrenadeBox[1].ul.y = MFD_GRENADE_BOX_Y;
    MfdGrenadeBox[1].lr.x = MFD_GRENADE_BOX_X2 + MFD_GRENADE_BOX_W;
    MfdGrenadeBox[1].lr.y = MFD_GRENADE_BOX_Y + MFD_GRENADE_BOX_H;

    return OK;
}

//--------------------------------------------------------------
// Like mini-expose, but gets the name for you, and
// conforms to our rect-o-tronic update facility

void mfd_item_micro_expose(uchar full, int triple) {
    if (!full_game_3d) {
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        ss_bitmap(&mfd_background, 0, 0);
        // gr_bitmap(&mfd_background, 0, 0);
    }
    if (full) {
        LGPoint siz;
        int id;
        short y = 2;
        char buf[LNAME_BUFSIZE];
        mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);

        get_object_long_name(triple, buf, LNAME_BUFSIZE);
        siz = mfd_draw_string(buf, X_MARGIN, y, GREEN_YELLOW_BASE, TRUE);
        y += siz.y + 2;

        // Draw the appropriate weapon art
        id = mfd_bmap_id(triple);
        if (RefIndexValid((RefTable *)ResGet(REFID(id)), REFINDEX(id)))
            draw_raw_resource_bm(id, (MFD_VIEW_WID - res_bm_width(id)) / 2, y);
        else
            ResUnlock(REFID(id));
    }
    return;
}

//--------------------------------------------------------------
// Called by things that know they have hi-res art to display.

void mfd_item_micro_hires_expose(uchar full, int triple) {
    if (!full_game_3d) {
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        gr_bitmap(&mfd_background, 0, 0);
    }
    if (full) {
        LGPoint siz;
        int id;
        short y = 2;
        char buf[LNAME_BUFSIZE];

        mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);

        get_object_long_name(triple, buf, LNAME_BUFSIZE);
        siz = mfd_draw_string(buf, X_MARGIN, y, GREEN_YELLOW_BASE, TRUE);
        y += siz.y + 2;

        id = mfd_bmap_id(triple);
        if (RefIndexValid((RefTable *)ResGet(REFID(id)), REFINDEX(id)))
            draw_hires_resource_bm(id, (SCONV_X(MFD_VIEW_WID) - res_bm_width(id)) / 2, SCONV_Y(y));
        else
            ResUnlock(REFID(id));
    }
    return;
}

// --------------------------------------------------------------------------
// mfd_item_draw_grenade_setting_boxes()
//
// Draws the little increment/decrement boxes on either side of the
// "time left before grenade blows up" setting on the MFD.

#ifdef OLD_GRENADE_BUTTONS
void mfd_item_draw_grenade_setting_boxes(MFD *m) {
    char buf[2];
    int i;

    for (i = 0; i < 2; i++) {
        gr_set_fcolor(WHITE);
        ss_rect(MfdGrenadeBox[i].ul.x, MfdGrenadeBox[i].ul.y, MfdGrenadeBox[i].lr.x, MfdGrenadeBox[i].lr.y);
        gr_set_fcolor(MFD_BTTN_FLASH);
        ss_box(MfdGrenadeBox[i].ul.x, MfdGrenadeBox[i].ul.y, MfdGrenadeBox[i].lr.x, MfdGrenadeBox[i].lr.y);
    }

    gr_set_fcolor(BLACK);
    sprintf(buf, "-");
    ss_string("-", MfdGrenadeBox[0].ul.x + 1, MfdGrenadeBox[0].ul.y + 1);
    sprintf(buf, "+");
    ss_string(buf, MfdGrenadeBox[1].ul.x + 1, MfdGrenadeBox[1].ul.y + 1);

    return;
}
#endif // OLD_GRENADE_BUTTONS

// --------------------------------------------------------------------------
// mfd_item_expose()
//
// Draws an overlay of a drug molecule or whatever in the current slot.

#define NULL_ACTIVE 0xFF
static ubyte cat2active[MFD_INV_CATEGORIES] = {
    NULL_ACTIVE,   ACTIVE_DRUG,    ACTIVE_HARDWARE,    ACTIVE_GRENADE,      ACTIVE_CART,
    ACTIVE_WEAPON, ACTIVE_GENERAL, ACTIVE_COMBAT_SOFT, ACTIVE_DEFENSE_SOFT, ACTIVE_MISC_SOFT,
};

#define TRASH_BUTTON REF_IMG_DiscardButton

#define NULL_REF MKREF(ID_NULL, 0)

Ref smallstuffSpews[] = {NULL_REF, NULL_REF, NULL_REF, REF_STR_gearSpew0,
                         NULL_REF, NULL_REF, NULL_REF, REF_STR_plotSpew0};

void mfd_general_inv_expose(MFD *mfd, ubyte control, ObjID id, uchar full) {
    int triple;
    Ref spew = NULL_REF;

    if (id == OBJ_NULL) {
        draw_blank_mfd();
        return;
    }
    triple = ID2TRIP(id);
    mfd_item_micro_expose(full, triple);
    if (full && !(ObjProps[OPNUM(id)].flags & INVENTORY_GENERAL)) {
        short x = (MFD_VIEW_WID - res_bm_width(TRASH_BUTTON)) / 2;
        short y = (MFD_VIEW_HGT - res_bm_height(TRASH_BUTTON)) / 2;
        draw_raw_resource_bm(TRASH_BUTTON, x, y);
        mfd_add_rect(x, y, x + res_bm_width(TRASH_BUTTON), y + res_bm_height(TRASH_BUTTON));
    }
    if (objs[id].obclass == CLASS_SMALLSTUFF)
        spew = smallstuffSpews[objs[id].subclass];
    if (spew != NULL_REF)
        draw_mfd_item_spew(spew + objs[id].info.type, 1);
}

uchar mfd_general_inv_handler(MFD *m, uiEvent *ev, int row) {
    ObjID id = player_struct.inventory[row];
    if (ev->type != UI_EVENT_MOUSE || !(ev->subtype & MOUSE_LDOWN))
        return FALSE;
    if (!(ObjProps[OPNUM(id)].flags & INVENTORY_GENERAL)) {
        LGPoint pos = MakePoint(ev->pos.x - m->rect.ul.x, ev->pos.y - m->rect.ul.y);
        short x = (MFD_VIEW_WID - res_bm_width(TRASH_BUTTON)) / 2;
        short y = (MFD_VIEW_HGT - res_bm_height(TRASH_BUTTON)) / 2;
        LGRect r;
        r.ul = r.lr = MakePoint(x, y);
        r.lr.x += res_bm_width(TRASH_BUTTON);
        r.lr.y += res_bm_height(TRASH_BUTTON);
        if (RECT_TEST_PT(&r, pos)) {
            int i;
            obj_destroy(id);
            for (i = row + 1; i < NUM_GENERAL_SLOTS; i++)
                player_struct.inventory[i - 1] = player_struct.inventory[i];
            player_struct.inventory[NUM_GENERAL_SLOTS - 1] = OBJ_NULL;
            drain_energy(1);
            mfd_notify_func(MFD_EMPTY_FUNC, MFD_ITEM_SLOT, TRUE, MFD_EMPTY, FALSE);
            chg_set_flg(INVENTORY_UPDATE);
        }
    }
    return TRUE;
}

#define STRINGS_PER_WARE (REF_STR_wareSpew1 - REF_STR_wareSpew0)
//#define SPEW_VERT_MARGIN 10
#define SPEW_VERT_MARGIN 2

void draw_mfd_item_spew(Ref id, int n) {
    uchar oldwrap = mfd_string_wrap;
    short w, h;
    short x, y;
    char buf[256];
    int i;

    gr_set_font(ResLock(MFD_FONT));
    buf[0] = '\0';
#ifdef CONCATENATE_ITEMSPEW
    for (i = 0; i < n; i++, id++)
#else
    i = n - 1;
    id += i;
#endif
        get_string(id, buf + strlen(buf), sizeof(buf) - strlen(buf));
    gr_string_wrap(buf, MFD_VIEW_WID - 2);
    gr_string_size(buf, &w, &h);
    x = (MFD_VIEW_WID - w) / 2;
    y = (MFD_VIEW_HGT - h - SPEW_VERT_MARGIN) / 2 + SPEW_VERT_MARGIN;
    mfd_string_wrap = FALSE;
    mfd_full_draw_string(buf, x, y, GREEN_YELLOW_BASE, MFD_FONT, TRUE, TRUE);
    mfd_string_wrap = oldwrap;
    ResUnlock(MFD_FONT);
}

void mfd_item_expose(MFD *m, ubyte control) {
    ubyte lastclass, currclass, lasttype, currtype = MFD_INV_NOTYPE;
    uchar FullRedraw = FALSE;
    int triple;

    currclass = MFDGetCurrItemClass(m->id);
    lastclass = MFDGetLastItemClass(m->id);

    if (cat2active[currclass] != NULL_ACTIVE)
        currtype = player_struct.actives[cat2active[currclass]];
    if (currtype == MFD_INV_NOTYPE)
        currclass = MFD_INV_NULL;
    lasttype = MFDGetLastItemType(m->id);
    MFDSetCurrItemType(m->id, currtype);

    MFDSetLastItemClass(m->id, currclass);
    MFDSetLastItemType(m->id, MFDGetCurrItemType(m->id));

    if (control & MFD_EXPOSE) {

        if ((control & MFD_EXPOSE_FULL) || (currclass != lastclass) || (currtype != lasttype))
            FullRedraw = TRUE;

        // If there is no currently selected member of the current class,
        // draw the blank screen thingie
        if (MFDGetCurrItemType(m->id) == EMPTY_WEAPON_SLOT) {
            mfd_expose_blank(m, control);
            return;
        }

        PUSH_CANVAS(pmfd_canvas);
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        mfd_clear_rects();

        switch (currclass) {

        case MFD_INV_NULL:

            if (FullRedraw) {
                draw_blank_mfd();
            }
            break;

        case MFD_INV_DRUG:

            triple = get_triple_from_class_nth_item(CLASS_DRUG, currtype);

            mfd_item_micro_expose(FullRedraw, triple);
            // mfd_item_micro_hires_expose(FullRedraw, triple);
            if (FullRedraw)
                draw_mfd_item_spew(REF_STR_drugSpew0 + currtype, 1);
            draw_res_bm(REF_IMG_Apply, DRUG_BUTTON_X, DRUG_BUTTON_Y);
            mfd_add_rect(DRUG_BUTTON_X, DRUG_BUTTON_Y, DRUG_BUTTON_X + DRUG_BUTTON_W, MFD_VIEW_HGT);
            break;

        case MFD_INV_GRENADE:

            triple = get_triple_from_class_nth_item(CLASS_GRENADE, currtype);
            mfd_item_micro_expose(FullRedraw, triple);
            // mfd_item_micro_hires_expose(FullRedraw, triple);
            break;

        case MFD_INV_HARDWARE:

            //            if (currtype == HARDWARE_DATA_READER) {
            //               ss_safe_set_cliprect(0,0,MFD_VIEW_WID,MFD_VIEW_HGT);
            //               ss_bitmap(&mfd_background,0,0);
            //               mfd_draw_string("Video reader",5,10,WHITE,TRUE);
            //               mfd_draw_string("Text reader",5,15,WHITE,TRUE);
            //               mfd_draw_string("Email reader",5,20,WHITE,TRUE);
            //            }
            //            else
            {
                int id;
                short x = HARDWARE_BUTTON_X, y = HARDWARE_BUTTON_Y;
                triple = get_triple_from_class_nth_item(CLASS_HARDWARE, currtype);

                mfd_item_micro_expose(FullRedraw, triple);
                // mfd_item_micro_hires_expose(FullRedraw, triple);
                if (!is_passive_hardware(currtype)) {
                    id = (player_struct.hardwarez_status[currtype] & WARE_ON) ? REF_IMG_Active : REF_IMG_Inactive;
                    draw_res_bm(id, x, y);
                    mfd_add_rect(x, y, x + HARDWARE_BUTTON_W, MFD_VIEW_HGT);
                }
                if (FullRedraw) {
                    uchar version = player_struct.hardwarez[currtype];
                    draw_mfd_item_spew(REF_STR_wareSpew0 + STRINGS_PER_WARE * currtype, version);
                }
            }

            break;

        case MFD_INV_AMMO:
            mfd_ammo_expose(control);
            break;

        case MFD_INV_SOFT_COMBAT:
        case MFD_INV_SOFT_DEFENSE:
            triple = get_ware_triple(currclass - MFD_INV_SOFT_COMBAT + WARE_SOFT_COMBAT, currtype);
            mfd_item_micro_expose(FullRedraw, triple);
            break;
        case MFD_INV_SOFT_MISC:
            // Monkey see, monkey do, monkey will destroy you.
            {
                short x = HARDWARE_BUTTON_X, y = HARDWARE_BUTTON_Y;
                triple = get_ware_triple(currclass - MFD_INV_SOFT_COMBAT + WARE_SOFT_COMBAT, currtype);

                mfd_item_micro_expose(FullRedraw, triple);
                if (is_oneshot_misc_software(currtype)) {
                    draw_res_bm(REF_IMG_Activate, x, y);
                    mfd_add_rect(x, y, x + HARDWARE_BUTTON_W, MFD_VIEW_HGT);
                }
            }
            break;
        case MFD_INV_GENINV:
            mfd_general_inv_expose(m, control, player_struct.inventory[currtype], FullRedraw);
            break;

        default:
            break;
        }

        POP_CANVAS();

        mfd_update_rects(m);
    }
}

// ---------------------------------------------------------------------------
// mfd_item_handler()
//
// Handle clicks to, for now, the +- grenade set-time boxes.

uchar mfd_item_handler(MFD *m, uiEvent *e) {
    uchar retval = FALSE;

    if (!(e->mouse_data.action & MOUSE_LDOWN))
        return retval;

    switch (MFDGetCurrItemClass(m->id)) {

    case MFD_INV_GRENADE:

#ifdef OLD_GRENADE_BUTTONS
    {
        int i;
        LGRect r[2];
        int triple;
        ubyte min, max;
        triple = get_triple_from_class_nth_item(CLASS_GRENADE, MFDGetCurrItemType(m->id));
        if (!(TRIP2SC(triple) == GRENADE_SUBCLASS_TIMED))
            return retval;

        min = TimedGrenadeProps[SCTRIP(triple)].min_time_set;
        max = TimedGrenadeProps[SCTRIP(triple)].max_time_set;

        RECT_OFFSETTED_RECT(&MfdGrenadeBox[0], m->rect.ul, &(r[0]));
        RECT_OFFSETTED_RECT(&MfdGrenadeBox[1], m->rect.ul, &(r[1]));

        for (i = 0; i < 2; i++) {

            if (RECT_TEST_PT(&r[i], e->pos)) {

                if (i == 0) {
                    if (player_struct.grenades_time_setting[MFDGetCurrItemType(m->id)] > min)
                        player_struct.grenades_time_setting[MFDGetCurrItemType(m->id)]--;
                } else if (i == 1) {
                    if (player_struct.grenades_time_setting[MFDGetCurrItemType(m->id)] < max)
                        player_struct.grenades_time_setting[MFDGetCurrItemType(m->id)]++;
                }

                mfd_notify_func(MFD_ITEM_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
            }
        }
    }
#endif // OLD_GRENADE_BUTTONS
    break;
    case MFD_INV_HARDWARE: {
        LGRect r = {{HARDWARE_BUTTON_X, HARDWARE_BUTTON_Y},
                    {HARDWARE_BUTTON_X + HARDWARE_BUTTON_W, HARDWARE_BUTTON_Y + HARDWARE_BUTTON_H}};
        RECT_OFFSETTED_RECT(&r, m->rect.ul, &r);
        if (RECT_TEST_PT(&r, e->pos)) {
            use_ware(WARE_HARD, player_struct.actives[ACTIVE_HARDWARE]);
            mfd_notify_func(MFD_ITEM_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
            retval = TRUE;
        }
    } break;
    case MFD_INV_SOFT_MISC: {
        LGRect r = {{HARDWARE_BUTTON_X, HARDWARE_BUTTON_Y},
                    {HARDWARE_BUTTON_X + HARDWARE_BUTTON_W, HARDWARE_BUTTON_Y + HARDWARE_BUTTON_H}};
        RECT_OFFSETTED_RECT(&r, m->rect.ul, &r);
        if (RECT_TEST_PT(&r, e->pos)) {
            use_ware(WARE_SOFT_MISC, player_struct.actives[ACTIVE_MISC_SOFT]);
            mfd_notify_func(MFD_ITEM_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
            retval = TRUE;
        }
    } break;
    case MFD_INV_DRUG: {
        LGRect r = {{DRUG_BUTTON_X, DRUG_BUTTON_Y}, {DRUG_BUTTON_X + DRUG_BUTTON_W, DRUG_BUTTON_Y + DRUG_BUTTON_H}};
        RECT_OFFSETTED_RECT(&r, m->rect.ul, &r);
        // Why is it that for wares, it's use_drug whereas for drugs it's drug_use?  Perhaps we'll never know.
        if (RECT_TEST_PT(&r, e->pos)) {
            drug_use(player_struct.actives[ACTIVE_DRUG]);
            mfd_notify_func(MFD_ITEM_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
            retval = TRUE;
        }
    } break;
    case MFD_INV_GENINV:
        mfd_general_inv_handler(m, e, player_struct.actives[ACTIVE_GENERAL]);
        break;

    case MFD_INV_AMMO:
        mfd_ammo_handler(m, e);
        break;
    }

    return retval;
}

// ----------------------
// * ITEM MFD FOR LANTERN
// ----------------------

#define LANTERN_BARRAY_X 2
#define LANTERN_BARRAY_WD (MFD_VIEW_WID - 6)
#define LANTERN_BARRAY_Y 45

#define LANTERN_LAST_SETTING(mfd) (player_struct.mfd_func_data[MFD_LANTERN_FUNC][mfd])
#define LANTERN_LAST_VERSION(mfd) (player_struct.mfd_func_data[MFD_LANTERN_FUNC][NUM_MFDS + mfd])
#define LANTERN_LAST_STATE(mfd) (player_struct.mfd_func_data[MFD_LANTERN_FUNC][2 * NUM_MFDS + mfd])
#define LANTERN_BARRAY_IDX 0

extern uchar muzzle_fire_light;

int energy_cost(int warenum);

uchar mfd_lantern_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data) {
    int n = CPTRIP(LANTERN_HARD_TRIPLE);
    int s = player_struct.hardwarez_status[n];
    void mfd_lantern_setting(int setting);

    if (bttn.x >= player_struct.hardwarez[n] || !(ev->subtype & MOUSE_LDOWN))
        return FALSE; // Version too high
    if (bttn.x == LAMP_SETTING(s)) {
        use_ware(WARE_HARD, n);
        return TRUE;
    }
    mfd_lantern_setting(bttn.x);
    return TRUE;
}

void mfd_lantern_setting(int setting) {
    int n = CPTRIP(LANTERN_HARD_TRIPLE);
    int s = player_struct.hardwarez_status[n];

    if (s & WARE_ON)
        set_player_energy_spend(player_struct.energy_spend - energy_cost(n));
    LAMP_SETTING_SET(s, setting);
    player_struct.hardwarez_status[n] = s;
    if (!muzzle_fire_light) {
        player_struct.light_value = s;
        lamp_set_vals();
    }
    if (player_struct.hardwarez_status[n] & WARE_ON)
        set_player_energy_spend(lg_min(MAX_ENERGY, (int)player_struct.energy_spend + energy_cost(n)));
    mfd_notify_func(MFD_LANTERN_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
}

errtype mfd_lanternware_init(MFD_Func *f) {
    int cnt = 0;
    LGPoint bsize;
    LGPoint bdims;
    LGRect r;
    errtype err;
    bsize.x = res_bm_width(REF_IMG_LitLamp0);
    bsize.y = res_bm_height(REF_IMG_LitLamp0);
    bdims.x = LAMP_VERSIONS;
    bdims.y = 1;
    r.ul.x = LANTERN_BARRAY_X;
    r.ul.y = LANTERN_BARRAY_Y;
    r.lr.x = r.ul.x + LANTERN_BARRAY_WD;
    r.lr.y = r.ul.y + bsize.y;
    err = MFDBttnArrayInit(&f->handlers[cnt++], &r, bdims, bsize, mfd_lantern_button_handler, NULL);
    if (err != OK)
        return err;
    f->handler_count = cnt;
    return OK;
}

void mfd_lanternware_expose(MFD *mfd, ubyte control) {
    int n, s, v;
    uchar full = control & MFD_EXPOSE_FULL;
    if (control == 0)
        return;
    n = CPTRIP(LANTERN_HARD_TRIPLE);
    s = player_struct.hardwarez_status[n];
    v = player_struct.hardwarez[n];
    PUSH_CANVAS(pmfd_canvas);
    mfd_clear_rects();
    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    if (!full_game_3d)
        ss_bitmap(&mfd_background, 0, 0);
    // gr_bitmap(&mfd_background, 0, 0);
    mfd_item_micro_expose(full, LANTERN_HARD_TRIPLE);
    // mfd_item_micro_hires_expose(full,LANTERN_HARD_TRIPLE);
    if (full)
        draw_mfd_item_spew(REF_STR_wareSpew0 + STRINGS_PER_WARE * n, v);
    if (full || LAMP_SETTING(s) != LANTERN_LAST_SETTING(mfd->id) || LANTERN_LAST_VERSION(mfd->id) != v ||
        LANTERN_LAST_STATE(mfd->id) != (s & WARE_ON)) {
        int xjump = LANTERN_BARRAY_WD - res_bm_width(REF_IMG_LitLamp0);
        int i;
        for (i = 0; i < v; i++) {
            int lit = i == LAMP_SETTING(s) && (s & WARE_ON);
            int id = (lit) ? REF_IMG_LitLamp0 : REF_IMG_UnlitLamp0;
            short x = LANTERN_BARRAY_X + i * xjump / (LAMP_VERSIONS - 1);
            short y = LANTERN_BARRAY_Y;
            draw_raw_resource_bm(id + i, x, LANTERN_BARRAY_Y);
            if (i == LAMP_SETTING(s)) {
                gr_set_fcolor(GREEN_BASE + 2);
                ss_box(x - 1, y - 1, x + res_bm_width(id + i) + 1, y + res_bm_height(id + i) + 1);
            }
        }
        mfd_add_rect(LANTERN_BARRAY_X - 2, LANTERN_BARRAY_Y - 2, LANTERN_BARRAY_X + LANTERN_BARRAY_WD + 2,
                     MFD_VIEW_HGT);
        LANTERN_LAST_SETTING(mfd->id) = LAMP_SETTING(s);
        LANTERN_LAST_VERSION(mfd->id) = v;
        LANTERN_LAST_STATE(mfd->id) = s & WARE_ON;
    }
    POP_CANVAS();
    mfd_update_rects(mfd);
}

// ----------------------
// * ITEM MFD FOR SHIELD
// ----------------------

#define SHIELD_BARRAY_X 2
#define SHIELD_BARRAY_WD (MFD_VIEW_WID - 4)
#define SHIELD_BARRAY_Y 45

#define SHIELD_LAST_STATUS(mfd) (player_struct.mfd_func_data[MFD_SHIELD_FUNC][mfd])
#define SHIELD_LAST_VERSION(mfd) (player_struct.mfd_func_data[MFD_SHIELD_FUNC][NUM_MFDS + mfd])
#define SHIELD_BARRAY_IDX 0

#define SHIELD_SETTINGS 3

void mfd_shield_setting(int setting);
uchar mfd_shield_handler(MFD *m, uiEvent *e);
uchar mfd_shield_button_handler(MFD *m, LGPoint bttn, uiEvent *ev, void *data);
errtype mfd_shield_init(MFD_Func *f);
void mfd_shieldware_expose(MFD *mfd, ubyte control);

uchar mfd_shield_handler(MFD *m, uiEvent *e) {
    uchar retval = FALSE;
    LGPoint pos = e->pos;
    ubyte n = CPTRIP(SHIELD_HARD_TRIPLE);
    ubyte v = player_struct.hardwarez[n];
    if (v != SHIELD_VERSIONS) // are we at max version
        return FALSE;
    if (!(e->subtype & MOUSE_LDOWN))
        return FALSE;
    pos.x -= m->rect.ul.x - SHIELD_BARRAY_X;
    pos.y -= m->rect.ul.y - SHIELD_BARRAY_Y;
    if (pos.x > 0 && pos.x < SHIELD_BARRAY_WD && pos.y > 0) {
        use_ware(WARE_HARD, n);
        mfd_shield_setting(v - 1);
        retval = TRUE;
    }
    return retval;
}

uchar mfd_shield_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data) {
    int n = CPTRIP(SHIELD_HARD_TRIPLE);
    int s = player_struct.hardwarez_status[n];

    if (bttn.x >= player_struct.hardwarez[n] || !(ev->subtype & MOUSE_LDOWN))
        return FALSE; // Version too high
    if (SHIELD_SETTING(s) == bttn.x) {
        use_ware(WARE_HARD, n);
        return TRUE;
    }
    mfd_shield_setting(bttn.x);
    return TRUE;
}

void mfd_shield_setting(int setting) {
    int n = CPTRIP(SHIELD_HARD_TRIPLE);
    int s = player_struct.hardwarez_status[n];

    if (s & WARE_ON)
        set_player_energy_spend(player_struct.energy_spend - energy_cost(n));
    SHIELD_SETTING_SET(s, setting);
    player_struct.hardwarez_status[n] = s;
    if (s & WARE_ON) {
        shield_set_absorb();
        set_player_energy_spend(lg_min(MAX_ENERGY, (int)player_struct.energy_spend + energy_cost(n)));
    }
    mfd_notify_func(MFD_SHIELD_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
}

errtype mfd_shield_init(MFD_Func *f) {
    int cnt = 0;
    LGPoint bsize;
    LGPoint bdims;
    LGRect r;
    errtype err;
    bsize.x = res_bm_width(REF_IMG_LitShield0);
    bsize.y = res_bm_height(REF_IMG_LitShield0);
    bdims.x = SHIELD_SETTINGS;
    bdims.y = 1;
    r.ul.x = SHIELD_BARRAY_X;
    r.ul.y = SHIELD_BARRAY_Y;
    r.lr.x = r.ul.x + SHIELD_BARRAY_WD;
    r.lr.y = r.ul.y + bsize.y;
    err = MFDBttnArrayInit(&f->handlers[cnt++], &r, bdims, bsize, mfd_shield_button_handler, NULL);
    if (err != OK)
        return err;
    f->handler_count = cnt;
    return OK;
}

void mfd_shieldware_expose(MFD *mfd, ubyte control) {
    int n, s, v;
    uchar full = control & MFD_EXPOSE_FULL;
    if (control == 0)
        return;
    n = CPTRIP(SHIELD_HARD_TRIPLE);
    s = player_struct.hardwarez_status[n];
    v = player_struct.hardwarez[n];
    PUSH_CANVAS(pmfd_canvas);
    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    mfd_clear_rects();
    if (!full_game_3d)
        ss_bitmap(&mfd_background, 0, 0);
    // gr_bitmap(&mfd_background, 0, 0);
    mfd_item_micro_expose(full, SHIELD_HARD_TRIPLE);
    // mfd_item_micro_hires_expose(full,SHIELD_HARD_TRIPLE);
    if (full)
        draw_mfd_item_spew(REF_STR_wareSpew0 + STRINGS_PER_WARE * n, v);
    if (full || s != SHIELD_LAST_STATUS(mfd->id) || SHIELD_LAST_VERSION(mfd->id) != v) {
        int xjump = SHIELD_BARRAY_WD / SHIELD_SETTINGS;
        int i;
        if (v >= SHIELD_VERSIONS) {
            int id = (s & WARE_ON) ? REF_IMG_LitShieldSuper : REF_IMG_UnlitShieldSuper;
            draw_raw_resource_bm(id, SHIELD_BARRAY_X + (SHIELD_BARRAY_WD - res_bm_width(id)) / 2, SHIELD_BARRAY_Y);
            mfd_add_rect(SHIELD_BARRAY_X, SHIELD_BARRAY_Y, SHIELD_BARRAY_X + SHIELD_BARRAY_WD, MFD_VIEW_WID);
        } else
            for (i = 0; i < v; i++) {
                int id = (i == SHIELD_SETTING(s) && (s & WARE_ON)) ? REF_IMG_LitShield0 : REF_IMG_UnlitShield0;
                grs_bitmap *bm = lock_bitmap_from_ref(id + i);
                short x = SHIELD_BARRAY_X + i * xjump;
                short y = SHIELD_BARRAY_Y;
                mfd_draw_bitmap(bm, x, y);
                if (i == SHIELD_SETTING(s)) {
                    gr_set_fcolor(GREEN_BASE + 2);
                    ss_box(x - 1, y - 1, x + bm->w + 1, y + bm->h + 1);
                }
                mfd_add_rect(x - 2, y - 2, x + bm->w + 2, y + bm->h + 2);
                RefUnlock(id + i);
            }
        SHIELD_LAST_STATUS(mfd->id) = s;
        SHIELD_LAST_VERSION(mfd->id) = v;
    }
    POP_CANVAS();
    mfd_update_rects(mfd);
}

// ----------------------
// * ITEM MFD FOR MOTION WARE
// ----------------------

#define MOTION_BARRAY_WD (3 * MFD_VIEW_WID / 4)
#define MOTION_BARRAY_X ((MFD_VIEW_WID - MOTION_BARRAY_WD) / 2)
#define MOTION_BARRAY_Y 48

#define MOTION_LAST_STATUS(mfd) (player_struct.mfd_func_data[MFD_MOTION_FUNC][mfd])
#define MOTION_LAST_VERSION(mfd) (player_struct.mfd_func_data[MFD_MOTION_FUNC][NUM_MFDS + mfd])
#define MOTION_BARRAY_IDX 0

#define MOTION_SETTING LAMP_SETTING
#define MOTION_SETTING_SET LAMP_SETTING_SET

#define MOTION_BUTTONS 2

uchar mfd_motion_button_handler(MFD *m, LGPoint bttn, uiEvent *ev, void *data);
errtype mfd_motion_init(MFD_Func *f);
void mfd_motionware_expose(MFD *mfd, ubyte control);

uchar mfd_motion_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data) {
    int n = CPTRIP(MOTION_HARD_TRIPLE);
    int s = player_struct.hardwarez_status[n];
    //   if (bttn.x >= player_struct.hardwarez[n] ||
    if (!(ev->subtype & MOUSE_LDOWN))
        return FALSE;
    if (MOTION_SETTING(s) == bttn.x) {
        use_ware(WARE_HARD, n);
        return TRUE;
    }
    if (s & WARE_ON)
        set_player_energy_spend(player_struct.energy_spend - energy_cost(n));
    MOTION_SETTING_SET(s, bttn.x);
    player_struct.hardwarez_status[n] = s;
    if (!(s & WARE_ON)) {
        //      use_ware(WARE_HARD,n);
    } else {
        motionware_mode = bttn.x + 1;
        set_player_energy_spend(lg_min(MAX_ENERGY, (int)player_struct.energy_spend + energy_cost(n)));
    }
    mfd_notify_func(MFD_MOTION_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
    return TRUE;
}

errtype mfd_motion_init(MFD_Func *f) {
    int cnt = 0;
    LGPoint bsize;
    LGPoint bdims;
    LGRect r;
    errtype err;
    bsize.x = res_bm_width(REF_IMG_LitMotion0);
    bsize.y = res_bm_height(REF_IMG_LitMotion0);
    bdims.x = MOTION_BUTTONS;
    bdims.y = 1;
    r.ul.x = MOTION_BARRAY_X;
    r.ul.y = MOTION_BARRAY_Y;
    r.lr.x = r.ul.x + MOTION_BARRAY_WD;
    r.lr.y = r.ul.y + bsize.y;
    err = MFDBttnArrayInit(&f->handlers[cnt++], &r, bdims, bsize, mfd_motion_button_handler, NULL);
    if (err != OK)
        return err;
    f->handler_count = cnt;
    return OK;
}

void mfd_motionware_expose(MFD *mfd, ubyte control) {
    int n, s, v;
    uchar full = control & MFD_EXPOSE_FULL;
    if (control == 0)
        return;
    n = CPTRIP(MOTION_HARD_TRIPLE);
    s = player_struct.hardwarez_status[n];
    v = player_struct.hardwarez[n];
    PUSH_CANVAS(pmfd_canvas);
    mfd_clear_rects();
    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    mfd_item_micro_expose(full, MOTION_HARD_TRIPLE);
    // mfd_item_micro_hires_expose(full,MOTION_HARD_TRIPLE);
    if (full)
        draw_mfd_item_spew(REF_STR_wareSpew0 + STRINGS_PER_WARE * n, v);
    if (full || s != MOTION_LAST_STATUS(mfd->id) || MOTION_LAST_VERSION(mfd->id) != v) {
        int xjump = MOTION_BARRAY_WD / MOTION_BUTTONS;
        int i;
        for (i = 0; i < lg_min(v, MOTION_BUTTONS); i++) {
            int id = ((i == MOTION_SETTING(s) && (s & WARE_ON))) ? REF_IMG_LitMotion0 : REF_IMG_UnlitMotion0;
            grs_bitmap *bm = lock_bitmap_from_ref(id + i);
            short x = MOTION_BARRAY_X + i * xjump;
            short y = MOTION_BARRAY_Y;
            mfd_draw_bitmap(bm, x, y);
            if (i == MOTION_SETTING(s)) {
                gr_set_fcolor(GREEN_BASE + 2);
                ss_box(x - 1, y - 1, x + bm->w + 1, y + bm->h + 1);
            }
            mfd_add_rect(x - 2, y - 2, x + bm->w + 2, y + bm->h + 2);
            RefUnlock(id + i);
        }
        MOTION_LAST_STATUS(mfd->id) = s;
        MOTION_LAST_VERSION(mfd->id) = v;
    }
    POP_CANVAS();
    mfd_update_rects(mfd);
}

// -----------------
// TIMED GRENADE MFD
// -----------------

#define MFD_GRENADE_FUNC 10

#define GRENADE_TIME_UNIT 10 // fraction of second for each time second unit

#define GRENADE_SLIDER_X MFD_BEAM_RECT_X1
#define GRENADE_SLIDER_Y 51
#define GRENADE_SLIDER_W 70
#define GRENADE_SLIDER_H 5

#define GRENADE_SLIDER_IDX 0

#define GRENADE_MOUSE_CONSTRAINED (player_struct.mfd_func_data[MFD_GRENADE_FUNC][7])

#define GRENADE_HIRES_CUTOFF 100

uchar mfd_grenade_slider_handler(MFD *m, short val, uiEvent *ev, void *data);
uchar mfd_grenade_handler(MFD *m, uiEvent *ev);
errtype mfd_grenade_init(MFD_Func *f);
void mfd_grenade_expose(MFD *mfd, ubyte control);

uchar mfd_grenade_slider_handler(MFD *m, short val, uiEvent *ev, void *data) {
    int n = player_struct.actives[ACTIVE_GRENADE];
    int triple = nth_after_triple(MAKETRIP(CLASS_GRENADE, 0, 0), n);
    short min = TimedGrenadeProps[SCTRIP(triple)].min_time_set * GRENADE_TIME_UNIT;
    short max = TimedGrenadeProps[SCTRIP(triple)].max_time_set * GRENADE_TIME_UNIT;
    short width = GRENADE_SLIDER_W;
    short setting;

    if (max > 2 * GRENADE_HIRES_CUTOFF) {
        width /= 2;
        if (val >= GRENADE_SLIDER_W / 2) {
            val -= GRENADE_SLIDER_W / 2;
            min = GRENADE_HIRES_CUTOFF;
        } else
            max = GRENADE_HIRES_CUTOFF;
    }
    setting = val * (max - min) / width + min;
    player_struct.grenades_time_setting[n] = setting;
    if (ev->subtype & MOUSE_LDOWN) {
        LGRect r = mfd_funcs[MFD_GRENADE_FUNC].handlers[GRENADE_SLIDER_IDX].r;
        int my = ev->pos.y;
        RECT_OFFSETTED_RECT(&r, m->rect.ul, &r);
        slider_cursor.hotspot.x = slider_cursor_bmap.w / 2;
        slider_cursor.hotspot.y = (slider_cursor_bmap.h / 2) + my - (r.ul.y + r.lr.y) / 2;
        ui_mouse_constrain_xy(r.ul.x, my, r.lr.x - 2, my);

        GRENADE_MOUSE_CONSTRAINED = m->id + 1;
        // Get our funky mfd-beam-phaser-setting cursor
#ifdef CURSOR_BACKUPS
        backup[20] = (uchar *)malloc(f->bm.w * f->bm.h);
        LG_memcpy(backup[20], f->bm.bits, f->bm.w * f->bm.h);
        gr_init_bm(&backup_mfd_cursor, backup[14], BMT_FLAT8, 0, mfd_cursor.w, mfd_cursor.h);
#endif
        uiPushRegionCursor(MFD_REGION(m), &slider_cursor);
    }
    if ((ev->mouse_data.buttons & (1 << MOUSE_LBUTTON)) == 0) {
        uiCursorStack *cs;
        uiGetRegionCursorStack(MFD_REGION(m), &cs);
        uiPopCursorEvery(cs, &slider_cursor);

        if (GRENADE_MOUSE_CONSTRAINED) {
            mouse_unconstrain();
            GRENADE_MOUSE_CONSTRAINED = 0;
            mfd_notify_func(MFD_GRENADE_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, TRUE);
        }
    }
    mfd_notify_func(MFD_GRENADE_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
    return TRUE;
}

uchar mfd_grenade_handler(MFD *m, uiEvent *ev) {
    LGRect r = mfd_funcs[MFD_GRENADE_FUNC].handlers[GRENADE_SLIDER_IDX].r;
    RECT_MOVE(&r, m->rect.ul);
    if (!RECT_TEST_PT(&r, ev->pos)) {
        uiCursorStack *cs;
        uiGetRegionCursorStack(MFD_REGION(m), &cs);
        uiPopCursorEvery(cs, &slider_cursor);
        mfd_notify_func(MFD_GRENADE_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
    }
    return FALSE;
}

errtype mfd_grenade_init(MFD_Func *f) {
    int cnt = 0;
    errtype err;
    LGRect r = {{GRENADE_SLIDER_X, GRENADE_SLIDER_Y},
                {GRENADE_SLIDER_X + GRENADE_SLIDER_W, GRENADE_SLIDER_Y + GRENADE_SLIDER_H}};
    err = MFDSliderInit(&f->handlers[cnt++], &r, mfd_grenade_slider_handler, NULL);
    if (err != OK)
        return err;
    f->handler_count = cnt;
#ifdef CURSOR_BACKUPS
    backup[21] = (uchar *)Malloc(slider_bmap.w * slider_bmap.h);
    LG_memcpy(backup[21], slider_bmap.bits, slider_bmap.w * slider_bmap.h);
    gr_init_bm(&backup_slider_cursor, backup[21], BMT_FLAT8, 0, slider_bmap.w, slider_bmap.h);
#endif
    return OK;
}

#define LAST_GRENADE(mfd) (player_struct.mfd_func_data[MFD_GRENADE_FUNC][mfd])
#define LAST_GRENADE_SETTING(mfd) (player_struct.mfd_func_data[MFD_GRENADE_FUNC][mfd + 2])

#define GRENADE_SLIDER_BORDER MFD_BEAMWPN_STAT_BORDER
#define GRENADE_SLIDER_SETTING_COLOR MFD_BEAMWPN_STAT_CHARGE

#define TIME_TEXT_Y (GRENADE_SLIDER_Y - 8)
#define TIME_TEXT_LEN 128

void mfd_grenade_expose(MFD *mfd, ubyte control) {
    MFD_Func *f = &mfd_funcs[MFD_GRENADE_FUNC];
    int n = player_struct.actives[ACTIVE_GRENADE];
    int triple = nth_after_triple(MAKETRIP(CLASS_GRENADE, 0, 0), n);
    uchar full = (control & MFD_EXPOSE_FULL) || (n != LAST_GRENADE(mfd->id));
    short setting = player_struct.grenades_time_setting[n];

    if (control == 0) {

        uiCursorStack *cs;
        uiGetRegionCursorStack(MFD_REGION(mfd), &cs);
        uiPopCursorEvery(cs, &slider_cursor);

        if (GRENADE_MOUSE_CONSTRAINED == mfd->id + 1) {
            mouse_unconstrain();
            GRENADE_MOUSE_CONSTRAINED = 0;
        }
        return;
    }
    mfd_clear_rects();
    PUSH_CANVAS(pmfd_canvas);
    ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    if (!full_game_3d)
        ss_bitmap(&mfd_background, 0, 0);
    // gr_bitmap(&mfd_background, 0, 0);
    mfd_item_micro_expose(full, triple);
    // mfd_item_micro_hires_expose(full,triple);
    LAST_GRENADE(mfd->id) = n;
    if (full || LAST_GRENADE_SETTING(mfd->id) != setting) {
        char buf[TIME_TEXT_LEN];
        LGRect *r = &f->handlers[GRENADE_SLIDER_IDX].r;
        short min = TimedGrenadeProps[SCTRIP(triple)].min_time_set * GRENADE_TIME_UNIT;
        short max = TimedGrenadeProps[SCTRIP(triple)].max_time_set * GRENADE_TIME_UNIT;
        short width = GRENADE_SLIDER_W;
        short base = 0;
        short x;
        if (max > 2 * GRENADE_HIRES_CUTOFF) {
            width /= 2;
            if (setting < GRENADE_HIRES_CUTOFF)
                max = GRENADE_HIRES_CUTOFF;
            else {
                base = GRENADE_SLIDER_W / 2;
                min = GRENADE_HIRES_CUTOFF;
            }
        }
        x = (setting - min) * width / (max - min) + base;
        gr_set_fcolor(GRENADE_SLIDER_BORDER);
        ss_box(r->ul.x - 1, r->ul.y - 1, r->lr.x, r->lr.y);
        mfd_add_rect(r->ul.x - 1, r->ul.y - 1, r->lr.x, r->lr.y + 1);
        gr_set_fcolor(GRENADE_SLIDER_SETTING_COLOR);
        if (!GRENADE_MOUSE_CONSTRAINED)
            draw_raw_resource_bm(REF_IMG_BeamSetting, r->ul.x + x - 3, r->ul.y - 1);


        sprintf(buf, "%s %d.%d", get_string(REF_STR_TimeSetting, NULL, TIME_TEXT_LEN), setting / 10, setting % 10);
        //numtostring(setting / 10, buf + strlen(buf)); // itoa(setting/10,buf+strlen(buf),10);
        //strcat(buf, ".");
        //numtostring(setting % 10, buf + strlen(buf)); // itoa(setting%10,buf+strlen(buf),10);
        {
            LGRect r = {{GRENADE_SLIDER_X, TIME_TEXT_Y}, {MFD_VIEW_WID, GRENADE_SLIDER_Y - 1}};
            mfd_partial_clear(&r);
        }
        mfd_draw_string(buf, GRENADE_SLIDER_X, TIME_TEXT_Y, GRENADE_SLIDER_BORDER, TRUE);
        LAST_GRENADE_SETTING(mfd->id) = setting;
    }
    POP_CANVAS();
    mfd_update_rects(mfd);
}

// ------------------
// * THE BIO WARE MFD
// ------------------

#define BIO_TEXT_X 29

// ---------------------------------------------------------------------------
// mfd_bioware_expose()
//
// This is the bioware, activated in the info window.  It displays stats.
// As of now, it's pretty sketchy.

#define LAST_HP(mfd) (mfd_fdata[MFD_BIOWARE_FUNC][4 * mfd])
#define LAST_FATIGUE(mfd) (mfd_fdata[MFD_BIOWARE_FUNC][1 + 4 * mfd])
#define LAST_DRUGBITS(mfd) (*(ushort *)&mfd_fdata[MFD_BIOWARE_FUNC][2 + 4 * mfd])

// this is stolen from gamesys.c
#define MAX_FATIGUE 10000
#define MAX_HP UCHAR_MAX

#define BIO_DRUG_UP 1   // experincing normal effects
#define BIO_DRUG_DOWN 2 // experiencing after effects.
#define BIO_DRUG_CLEAN 0

#define BITS_PER_DRUG 2

void mfd_bioware_expose(MFD *m, ubyte control) {
    uchar full = control & MFD_EXPOSE_FULL;
    int i, y = 2, triple;
    char buf2[12];
    LGRect r;

    r.ul.x = BIO_TEXT_X;
    r.ul.y = 20;
    r.lr.x = MFD_VIEW_WID;
    r.lr.y = MFD_VIEW_HGT;

    // turn off the bioware if there's no exposed mfd.
    {
        extern WARE HardWare[NUM_HARDWAREZ];
        uchar on = full || control & MFD_EXPOSE;
        for (i = 0; i < NUM_MFDS; i++) {
            ubyte slot = player_struct.mfd_current_slots[i];
            ubyte func = mfd_get_func(i, slot);
            if (func == MFD_BIOWARE_FUNC && (control != 0 || m->id != i))
                on = TRUE;
        }
        if (on == !(player_struct.hardwarez_status[HARDWARE_BIOWARE] & WARE_ON)) {
            use_ware(WARE_HARD, HARDWARE_BIOWARE);
        }
    }

    if (control & MFD_EXPOSE) {
        char *s;
        char pct[] = "%";
        short x;
        ubyte v = player_struct.hardwarez[HARDWARE_BIOWARE];
        int ref = MKREF(RES_mfdArtOverlays, MFD_ART_HUMAN);
        uchar stam = player_struct.drug_status[CPTRIP(STAMINA_DRUG_TRIPLE)] > 0;

        PUSH_CANVAS(pmfd_canvas);
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        mfd_clear_rects();
        if (!full_game_3d)
            ss_bitmap(&mfd_background, 0, 0);
        // gr_bitmap(&mfd_background, 0, 0);
        gr_set_font(ResLock(MFD_FONT));

        if (full) {
            draw_res_bm(ref, 0, 0);
            mfd_add_rect(0, 0, res_bm_width(ref), res_bm_height(ref));
        }
#ifdef BIOWARE_TITLE
        // Title
        if (full) {
            s = get_temp_string(REF_STR_BiowareTitle);
            mfd_draw_string(s, BIO_TEXT_X, y, GREEN_YELLOW_BASE, TRUE);
        }
        y += Y_STEP;
#endif

        // Health
        if (full || (LAST_HP(m->id) != player_struct.hit_points)) {
            uint hp;
            LGRect rest;
            s = get_temp_string(REF_STR_BiowareHealth);
            x = BIO_TEXT_X + gr_string_width(s);
            if (full)
                mfd_draw_string(s, BIO_TEXT_X, y, GREEN_YELLOW_BASE, TRUE);
            hp = (100 * player_struct.hit_points + (MAX_HP / 2)) / MAX_HP;
            if (hp == 0 && player_struct.hit_points > 0)
                hp = 1;
            sprintf(buf2, "%d", hp);

            // hack to save space in display.  Use "I" for "1", knowing
            // that "I" is narrower.
            string_replace_char(buf2, '1', 'I');

            mfd_draw_string(buf2, x, y, GREEN_YELLOW_BASE, TRUE);
            x += gr_string_width(buf2);
            mfd_draw_string(pct, x, y, GREEN_YELLOW_BASE, TRUE);
            x += gr_string_width(pct);
            rest.ul.x = x;
            rest.ul.y = y;
            rest.lr.x = MFD_VIEW_WID;
            rest.lr.y = y + Y_STEP;
            mfd_partial_clear(&rest);
            LAST_HP(m->id) = player_struct.hit_points;
        }
        y += Y_STEP;

        // Fatigue
        if (full || stam || (LAST_FATIGUE(m->id) != 100 * (uint)player_struct.fatigue / MAX_FATIGUE)) {
            LGRect rest;
            ubyte f = 100 * (uint)player_struct.fatigue / MAX_FATIGUE;
            s = get_temp_string(REF_STR_BiowareFatigue);
            x = BIO_TEXT_X + gr_string_width(s);
            if (full)
                mfd_draw_string(s, BIO_TEXT_X, y, GREEN_YELLOW_BASE, TRUE);
            if (stam) {
                strcpy(buf2, "--");
                mfd_draw_string(buf2, x, y, GREEN_YELLOW_BASE, TRUE);
                x += gr_string_width(buf2);
            } else {
                sprintf(buf2, "%d", f); // itoa(f,buf2,10);

                // hack to save space in display.  Use "I" for "1", knowing
                // that "I" is narrower.
                string_replace_char(buf2, '1', 'I');
                mfd_draw_string(buf2, x, y, GREEN_YELLOW_BASE, TRUE);
                x += gr_string_width(buf2);
                mfd_draw_string(pct, x, y, GREEN_YELLOW_BASE, TRUE);
                x += gr_string_width(pct);
            }

            rest.ul.x = x;
            rest.ul.y = y;
            rest.lr.x = MFD_VIEW_WID;
            rest.lr.y = y + Y_STEP;
            mfd_partial_clear(&rest);
            LAST_FATIGUE(m->id) = f;
        }
        y += Y_STEP;

        if (v > 1) {
            ushort drugbits = 0;
            for (i = 0; i < NUM_DRUGS; i++) {
                if (player_struct.drug_status[i] > 0)
                    drugbits |= (BIO_DRUG_UP << (i * BITS_PER_DRUG));
                if (player_struct.drug_status[i] < 0)
                    drugbits |= (BIO_DRUG_DOWN << (i * BITS_PER_DRUG));
            }

            if (full || drugbits != LAST_DRUGBITS(m->id)) {
                short savey = y;
                LAST_DRUGBITS(m->id) = drugbits;
                for (i = 0; i < NUM_DRUGS; i++, drugbits >>= BITS_PER_DRUG) {
                    ushort drugged = drugbits & ((1 << BITS_PER_DRUG) - 1);
                    if (drugged != BIO_DRUG_CLEAN) {
                        ubyte color = (drugged == BIO_DRUG_UP) ? GREEN_BASE + 3 : GOOD_RED;
                        triple = drug2triple(i);
                        get_object_short_name(triple, buf2, sizeof(buf2));
                        mfd_draw_string(buf2, BIO_TEXT_X, y, color, TRUE);
                        y += Y_STEP;
                    }
                }
                mfd_add_rect(BIO_TEXT_X, savey, MFD_VIEW_WID, savey + Y_STEP * NUM_DRUGS);
            }
        }
        if (full)
            mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);

        ResUnlock(MFD_FONT);

        POP_CANVAS();
        mfd_update_rects(m);
    }

    return;
}

// -------------------
// * THE ANIMATION MFD
// -------------------

// ---------------------------------------------------------------------------
// mfd_anim_init()
//
// Open the space station resource file for animation

errtype mfd_anim_init() {
#ifdef USING_DORKY_BROKEN_ANIM
    if (ResOpenFile("space4.res") < 0)
        critical_error(CRITERR_RES | 5);
#endif

    return OK;
}

// ---------------------------------------------------------------------------
// mfd_anim_expose()
//
// Strictly temporary code.  Starts or stops the space station animation.

void mfd_anim_expose(MFD *m, ubyte control) {
#ifndef NO_DUMMIES
    MFD *dummy;
    ubyte dummy2;
    dummy = m;
    dummy2 = control;
#endif
#ifdef USING_DORKY_BROKEN_ANIM
    static uchar AnimOn[2];
    static ActAnim *anim[2];

    if (control & MFD_EXPOSE) {

        gr_set_fcolor((long)BLACK);
        ss_rect(m->rect.ul.x, m->rect.ul.y, m->rect.lr.x, m->rect.lr.y);

        anim[m->id] = AnimPlayRegion(REF_ANIM_space4, &(m->reg), m->rect.ul, 0);
        chg_set_sta(ANIM_UPDATE);
        AnimOn[m->id] = TRUE;
    } else {

        AnimKill((anim[m->id]));
        AnimOn[m->id] = FALSE;
        if ((AnimOn[0] == FALSE) && (AnimOn[1] == FALSE))
            chg_unset_sta(ANIM_UPDATE);
    }
#endif
    return;
}

// SHODAN!!
// Note this expects all appropriate 2d preparation to already be done!!

errtype draw_shodan_influence(MFD *mfd, uchar amt);

errtype draw_shodan_influence(MFD *mfd, uchar amt) {
    char *s = get_temp_string(SHODAN_FAILURE_STRING);

    amt = lg_min(NUM_SHODAN_MUGS - 1, amt >> SHODAN_INTERVAL_SHIFT);
    draw_raw_res_bm_temp(REF_IMG_EmailMugShotBase + FIRST_SHODAN_MUG + amt, 0, 0);

    gr_set_font(ResLock(MFD_FONT));
    gr_string_wrap(s, MFD_VIEW_WID);
    mfd_draw_string(s, 2, 2, SHODAN_COLOR, TRUE);
    ResUnlock(MFD_FONT);
    gr_font_string_unwrap(s);

    mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    return (OK);
}

// ELEVATOR PANEL MFD
// ------------------

// NOMENCLATURE: "level" refers to the internal, unique, game-system
// number for a level.  "floor" refers to the in-game floor number for
// a floor.

#define NUM_ELEV_LVLS 15
#define NUM_ELEVATOR_BUTTONS 12
#define ELEV_BTTN_ROWS 4
#define ELEV_BTTN_COLS ((NUM_ELEVATOR_BUTTONS + ELEV_BTTN_ROWS - 1) / ELEV_BTTN_ROWS)
#define ELEV_BTTNS_X 11
#define ELEV_BTTNS_WD (MFD_VIEW_WID - 20)
#define ELEV_BTTNS_Y 21
#define ELEV_BTTNS_HT (MFD_VIEW_HGT - ELEV_BTTNS_Y - 2)
#define ELEV_BTTN_HT 8
#define ELEV_BTTN_WD 11
#define ELEV_STATUS_Y 3
#define ELEV_STATUS_X 50

#define ELEV_STATUS_FONT RES_mediumLEDFont
#define ELEV_STATUS_COLOR (GOOD_RED)

typedef struct _elev_data {
    ushort shownlvls; // level shown on the button panel (bitmask)
    ushort reachlvls; // level actually reachable        (bitmask)
    struct _mfd_specific {
        ubyte currlev : 4;  // current level shown
        ubyte selected : 4; // currently selected button
    } stat, mfd_last[NUM_MFDS];
} elev_data_type;

uchar curr_elev_special = 0;

errtype mfd_elevator_setlev(MFD *mfd, short lev, elev_data_type *elev_data);
uchar mfd_elevator_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data);
errtype mfd_elevator_init(MFD_Func *f);

errtype mfd_elevator_setlev(MFD *mfd, short lev, elev_data_type *elev_data) {
    elev_data->stat.currlev = lev;
    mfd_notify_func(MFD_ELEV_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
    mfd_update_current_slot(mfd->id, MFD_CHANGEBIT_FULL, 0);
    return (OK);
}

uchar mfd_elevator_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data) {
    elev_data_type *elev_data = (elev_data_type *)&player_struct.mfd_func_data[MFD_ELEV_FUNC][0];
    int b = bttn.x * ELEV_BTTN_ROWS + bttn.y;
    int c;
    ubyte l;
    ubyte reachl;
    ushort bit;

    if (!(ev->mouse_data.action & MOUSE_LDOWN))
        return FALSE;

    // If SHODAN has defeated us, indicate this for our expose func
    if (curr_elev_special) {
        elev_data->mfd_last[mfd->id].currlev = 0xF;
        mfd_notify_func(MFD_ELEV_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
        mfd_update_current_slot(mfd->id, MFD_CHANGEBIT_FULL, 0);
        return (OK);
    }

    for (c = 0, reachl = 0, l = 0, bit = 1; l < NUM_ELEV_LVLS; l++, bit = bit << 1) {
        if (elev_data->reachlvls & bit)
            reachl++;
        if (elev_data->shownlvls & bit) {
            c++;
            if (c > b)
                break;
        }
    }
    if (l >= NUM_ELEV_LVLS)
        return TRUE;
    elev_data->stat.selected = b;
#ifdef PLAYTEST
    mprintf("Pushing button %d\n", b);
#endif
    if (!(bit & elev_data->reachlvls)) {
        string_message_info(REF_STR_ElevatorNoMove);
#ifdef PLAYTEST
        mprintf("Can't get to that level\n");
#endif
    } else {
        if (me_bits_music(MAP_GET_XY(PLAYER_BIN_X, PLAYER_BIN_Y)) != ELEVATOR_ZONE)
            string_message_info(REF_STR_UseTooFar);
        else {
            int oldlev;
            oldlev = elev_data->stat.currlev;
            mfd_elevator_setlev(mfd, l, elev_data);
            if (!elevator_use(l, reachl - 1))
                mfd_elevator_setlev(mfd, oldlev, elev_data);
        }
    }
    return TRUE;
}

#define TEST_ELEVPANEL

errtype mfd_elevator_init(MFD_Func *f) {
    int cnt = 0;
    errtype err;
    LGPoint bsize = {ELEV_BTTN_WD, ELEV_BTTN_HT};
    LGPoint bdims = {ELEV_BTTN_COLS, ELEV_BTTN_ROWS};
    LGRect r = {{ELEV_BTTNS_X, ELEV_BTTNS_Y}, {ELEV_BTTNS_X + ELEV_BTTNS_WD, ELEV_BTTNS_Y + ELEV_BTTNS_HT}};
    err = MFDBttnArrayInit(&f->handlers[cnt++], &r, bdims, bsize, mfd_elevator_button_handler, NULL);
    if (err != OK)
        return err;
    f->handler_count = cnt;
#ifdef TEST_ELEVPANEL
    {
        elev_data_type *elev_data = (elev_data_type *)&player_struct.mfd_func_data[MFD_ELEV_FUNC][0];
        elev_data->shownlvls = 0xFFF;
        elev_data->reachlvls = 0xF0F;
    }
#endif
    return OK;
}

// Does every thing but set the slot..
void mfd_setup_elevator(ushort levmask, ushort reachmask, ushort curlevel, uchar special) {
    elev_data_type *elev_data = (elev_data_type *)&player_struct.mfd_func_data[MFD_ELEV_FUNC][0];
    elev_data->shownlvls = levmask;
    elev_data->reachlvls = reachmask;
    elev_data->stat.currlev = curlevel;
    elev_data->stat.selected = 0xF;
    curr_elev_special = special;
    mfd_notify_func(MFD_ELEV_FUNC, MFD_INFO_SLOT, TRUE, MFD_ACTIVE, TRUE);
}

char *level_to_floor(int lev_num, char *buf) {
    int bpos = 0;
    char grov_init = toupper(get_temp_string(REF_STR_GroveWord)[0]);

    switch (lev_num) {
    case 0:
        buf[bpos++] = toupper(get_temp_string(REF_STR_ReactorWord)[0]);
        break;
    case 11:
        buf[bpos++] = grov_init;
        buf[bpos++] = '4';
        break;
    case 12:
        buf[bpos++] = grov_init;
        buf[bpos++] = '1';
        break;
    case 13:
        buf[bpos++] = grov_init;
        buf[bpos++] = '2';
        break;
    default:
        if (lev_num >= 10) {
            buf[bpos++] = '0' + ((lev_num / 10) % 10);
            buf[bpos++] = '0' + (lev_num % 10);
        } else
            buf[bpos++] = '0' + lev_num;
        break;
    }
    if (bpos != 0)
        buf[bpos] = '\0'; // add trailing stop
    return (buf);
}

#define NUMBER_BUFSZ 4 // don't forget the terminator!

// if we null your panel_ref, return the value
// before we nulled it.  Otherwise, return NULL.
//
ObjID panel_ref_unexpose(int mfdid, int func) {
    uchar found = FALSE;
    int id = NUM_MFDS;
    ObjID pr = player_struct.panel_ref;

    while (mfd_yield_func(func, &id))
        if (id != mfdid)
            return OBJ_NULL;
        else
            found = TRUE;

    if (found)
        check_panel_ref(TRUE);
    else
        player_struct.panel_ref = OBJ_NULL;
    return pr;
}

void mfd_elevator_expose(MFD *mfd, ubyte control) {
    elev_data_type *elev_data = (elev_data_type *)&player_struct.mfd_func_data[MFD_ELEV_FUNC][0];
    char buf[NUMBER_BUFSZ];
    uchar full = control & MFD_EXPOSE_FULL;
    if (control == 0) {
        panel_ref_unexpose(mfd->id, MFD_ELEV_FUNC);
        return;
    }
    mfd_clear_rects();
    PUSH_CANVAS(pmfd_canvas);
    if (full)
        mfd_clear_view();
    if (curr_elev_special == 0)
        elev_data->mfd_last[mfd->id].currlev = 0;
    if (elev_data->mfd_last[mfd->id].currlev == 0xF)
        draw_shodan_influence(mfd, curr_elev_special);
    else {
        if (full || elev_data->mfd_last[mfd->id].currlev != elev_data->stat.currlev) {
            short w, h;
            int lev = elev_data->stat.currlev;
            gr_set_font(ResLock(ELEV_STATUS_FONT));
            level_to_floor(lev, buf);
            gr_string_size(buf, &w, &h);
            mfd_draw_font_string(buf, ELEV_STATUS_X - w, ELEV_STATUS_Y, ELEV_STATUS_COLOR, ELEV_STATUS_FONT, TRUE);
            elev_data->mfd_last[mfd->id].currlev = lev;
            ResUnlock(ELEV_STATUS_FONT);
        }
        if (full || elev_data->mfd_last[mfd->id].selected != elev_data->stat.selected) {
            int i;
            int l;
            short w, h;
            //         LGPoint bstep = { ELEV_BTTNS_WD/ELEV_BTTN_COLS,
            //                        ELEV_BTTNS_HT/ELEV_BTTN_ROWS };
            gr_set_font(ResLock(MFD_FONT));
            for (i = 0, l = 0; i < NUM_ELEVATOR_BUTTONS; i++) {
                ubyte clr;
                LGPoint bttn;

                bttn.x = ELEV_BTTNS_X + (i / ELEV_BTTN_ROWS) * (ELEV_BTTNS_WD - ELEV_BTTN_WD) / (ELEV_BTTN_COLS - 1);
                bttn.y = ELEV_BTTNS_Y + (i % ELEV_BTTN_ROWS) * (ELEV_BTTNS_HT - ELEV_BTTN_HT) / (ELEV_BTTN_ROWS - 1);

                if (i > 0)
                    l++;
                for (; l < NUM_ELEV_LVLS; l++)
                    if ((1 << l) & elev_data->shownlvls)
                        break;
                if (l >= NUM_ELEV_LVLS)
                    break;
                if (!(elev_data->reachlvls & (1 << l)))
                    clr = UNAVAILABLE_ITEM_COLOR;
                else if (i == elev_data->stat.selected)
                    clr = SELECTED_ITEM_COLOR;
                else
                    clr = ITEM_COLOR;
                gr_set_fcolor((long)clr);
                ss_box(bttn.x, bttn.y, bttn.x + ELEV_BTTN_WD, bttn.y + ELEV_BTTN_HT);
                gr_set_fcolor((long)ITEM_COLOR + 2);
                ss_box(bttn.x - 1, bttn.y - 1, bttn.x + ELEV_BTTN_WD + 1, bttn.y + ELEV_BTTN_HT + 1);
                level_to_floor(l, buf);
                gr_string_size(buf, &w, &h);
                mfd_draw_string(buf, bttn.x + (ELEV_BTTN_WD - w) / 2 + 1, bttn.y + 1, clr, TRUE);
            }
            elev_data->mfd_last[mfd->id].selected = elev_data->stat.selected;
            mfd_add_rect(ELEV_BTTNS_X, ELEV_BTTNS_Y, ELEV_BTTNS_X + ELEV_BTTNS_WD, ELEV_BTTNS_Y + ELEV_BTTNS_HT);
            ResUnlock(MFD_FONT);
        }
    }
    POP_CANVAS();
    mfd_update_rects(mfd);
}

// ------------------
// KEYPAD MFD
// ------------------

#define NUM_KEYPAD_BUTTONS 12
#define KEYPAD_BTTN_ROWS 4
#define KEYPAD_BTTN_COLS 3
#define KEYPAD_X_MARGIN 0
#define KEYPAD_Y_MARGIN 2
#define KEYPAD_BTTNS_X 19
#define KEYPAD_BTTNS_WD (MFD_VIEW_WID - (2 * KEYPAD_BTTNS_X) - 1 - KEYPAD_X_MARGIN)
//#define KEYPAD_BTTNS_WD    (MFD_VIEW_WID - (3* KEYPAD_BTTNS_X))
#define KEYPAD_BTTNS_Y 20
#define KEYPAD_BTTNS_HT (MFD_VIEW_HGT - KEYPAD_BTTNS_Y - KEYPAD_Y_MARGIN - 1)
#define KEYPAD_BTTN_HT 8
#define KEYPAD_BTTN_WD 11
#define KEYPAD_STATUS_Y 3
#define KEYPAD_STATUS_X 60

#define MAX_KEYPAD_DIGITS 3

#define KEYPAD_STATUS_FONT RES_mediumLEDFont
#define KEYPAD_STATUS_COLOR (GOOD_RED)

bool gKeypadOverride = false; // When this is true, don't move the player.

typedef struct _keypad_data {
    uchar curr_digit;
    uchar last_digit;
    uchar digits[MAX_KEYPAD_DIGITS];
    uchar special;
} keypad_data_type;

uchar keypad_num(int b);
char *keypad_name(int b, char *buf);
char *mfd_keypad_assemble(keypad_data_type *keypad_data, char *buf);
errtype mfd_keypad_input(MFD *m, char b_num);
uchar keypad_hotkey_func(ushort keycode, uint32_t context, intptr_t data);
uchar mfd_keypad_handler(MFD *m, uiEvent *ev);
uchar mfd_keypad_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data);
errtype mfd_keypad_init(MFD_Func *f);
void mfd_keypad_expose(MFD *mfd, ubyte control);

uchar keypad_num(int b) {
#ifdef KEYPAD_NUM_CASE
    uchar retval;
    // as Doug points out, this case statement is expressed algorithmically,
    // but hey, this is already done, easier to modify and maybe even easier
    // to understand.
    switch (b) {
    case 0:
        retval = 1;
        break;
    case 1:
        retval = 4;
        break;
    case 2:
        retval = 7;
        break;
    case 3:
        retval = 10;
        break;
    case 4:
        retval = 2;
        break;
    case 5:
        retval = 5;
        break;
    case 6:
        retval = 8;
        break;
    case 7:
        retval = 0;
        break;
    case 8:
        retval = 3;
        break;
    case 9:
        retval = 6;
        break;
    case 10:
        retval = 9;
        break;
    case 11:
        retval = 11;
        break;
    }
    return (retval);
#endif
    static uchar retval[] = {1, 4, 7, 10, 2, 5, 8, 0, 3, 6, 9, 11};
    return (retval[b]);
}

// note that the b in keypad_name is already converted from keypad_num
char *keypad_name(int b, char *buf) {
    switch (b) {
    case 10:
        strcpy(buf, "-");
        break;
    case 11:
        strcpy(buf, "C");
        break;
    default:
        sprintf(buf, "%d", b);
        break;
    }
    return (buf);
}

char *mfd_keypad_assemble(keypad_data_type *keypad_data, char *buf) {
    char tmp[5];
    int i;
    strcpy(buf, "");
    for (i = 0; i < keypad_data->curr_digit; i++) {
        strcat(buf, keypad_name(keypad_data->digits[i], tmp));
    }
    return (buf);
}

errtype mfd_keypad_input(MFD *mfd, char b_num) {
    keypad_data_type *keypad_data = (keypad_data_type *)&player_struct.mfd_func_data[MFD_KEYPAD_FUNC][0];

    switch (b_num) {
    case 10:
        if (keypad_data->curr_digit != 0)
            keypad_data->curr_digit--;
        break;
    case 11:
        keypad_data->curr_digit = 0;
        break;
    default:
        if (keypad_data->curr_digit == MAX_KEYPAD_DIGITS)
            mfd_setup_keypad(keypad_data->special);
        keypad_data->digits[keypad_data->curr_digit] = b_num;
        keypad_data->curr_digit++;
        break;
    }
    play_digi_fx_obj(SFX_MFD_KEYPAD, 1, player_struct.panel_ref);
    if (keypad_data->special == 0 && keypad_data->curr_digit == MAX_KEYPAD_DIGITS) {
        keypad_trigger(player_struct.panel_ref, keypad_data->digits);
    }
    mfd_notify_func(MFD_KEYPAD_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
    return (OK);
}

uchar keypad_hotkey_func(ushort keycode, uint32_t context, intptr_t data) {
    extern MFD mfd[];
    uchar digit = kb2ascii(keycode) - '0';
    int m = NUM_MFDS;
    while (mfd_yield_func(MFD_KEYPAD_FUNC, &m)) {
        mfd_keypad_input(&mfd[m], digit);
        return TRUE;
    }
    return FALSE;
}

void install_keypad_hotkeys(void) {
    int i;
    for (i = 0; i < 10; i++)
        // KLC      hotkey_add(('0'+i)|KB_FLAG_DOWN|KB_FLAG_2ND, DEMO_CONTEXT, keypad_hotkey_func, 0);
        hotkey_add(('0' + i) | KB_FLAG_DOWN, DEMO_CONTEXT, keypad_hotkey_func, 0);
}

uchar mfd_keypad_handler(MFD *m, uiEvent *ev) {
    uchar retval = FALSE;
    char n;

    if (ev->type != UI_EVENT_KBD_COOKED)
        return (FALSE);
    if (!(ev->cooked_key_data.code & KB_FLAG_DOWN))
        return (FALSE);
    n = (ev->cooked_key_data.code & 0xFF) - '0';
    if ((n < 0) || (n > 9))
        return (FALSE);
    mfd_keypad_input(m, n);
    return (FALSE);
}

uchar mfd_keypad_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data) {
    int b = bttn.x * KEYPAD_BTTN_ROWS + bttn.y;

    // Filter out anything that isn't a left-click down at all
    if ((ev->subtype & (MOUSE_LDOWN | UI_MOUSE_LDOUBLE)) == 0)
        return (FALSE);
    mfd_keypad_input(mfd, keypad_num(b));
    return TRUE;
}

errtype mfd_keypad_init(MFD_Func *f) {
    int cnt = 0;
    errtype err;
    LGPoint bsize = {KEYPAD_BTTN_WD, KEYPAD_BTTN_HT};
    LGPoint bdims = {KEYPAD_BTTN_COLS, KEYPAD_BTTN_ROWS};
    LGRect r = {{KEYPAD_BTTNS_X, KEYPAD_BTTNS_Y}, {KEYPAD_BTTNS_X + KEYPAD_BTTNS_WD, KEYPAD_BTTNS_Y + KEYPAD_BTTNS_HT}};
    err = MFDBttnArrayInit(&f->handlers[cnt++], &r, bdims, bsize, mfd_keypad_button_handler, NULL);
    if (err != OK)
        return err;
    f->handler_count = cnt;
    return OK;
}

// Does every thing but set the slot..
void mfd_setup_keypad(char special) {
    int i;
    keypad_data_type *keypad_data = (keypad_data_type *)&player_struct.mfd_func_data[MFD_KEYPAD_FUNC][0];
    keypad_data->curr_digit = 0;
    for (i = 0; i < MAX_KEYPAD_DIGITS; i++)
        keypad_data->digits[i] = 0;
    keypad_data->special = special;
    mfd_notify_func(MFD_KEYPAD_FUNC, MFD_INFO_SLOT, TRUE, MFD_ACTIVE, TRUE);
}

void mfd_keypad_expose(MFD *mfd, ubyte control) {
    keypad_data_type *keypad_data = (keypad_data_type *)&player_struct.mfd_func_data[MFD_KEYPAD_FUNC][0];
    char buf[NUMBER_BUFSZ];
    uchar full = control & MFD_EXPOSE_FULL;
    if (control == 0) {
        ObjID pr;
        pr = panel_ref_unexpose(mfd->id, MFD_KEYPAD_FUNC);
        if (pr)
            objs[pr].info.current_frame = 0;
        gKeypadOverride = FALSE;
        return;
    }
    mfd_clear_rects();
    PUSH_CANVAS(pmfd_canvas);
    if (full)
        mfd_clear_view();
    if ((keypad_data->special > 0) && (keypad_data->curr_digit > 0))
        draw_shodan_influence(mfd, keypad_data->special);
    else {
        if (full || (keypad_data->last_digit != keypad_data->curr_digit)) {
            int i;
            short w, h;
            //         LGPoint bstep = { KEYPAD_BTTNS_WD/KEYPAD_BTTN_COLS,
            //                        KEYPAD_BTTNS_HT/KEYPAD_BTTN_ROWS };

            // Draw cool LED at top of MFD
            gr_set_font(ResLock(KEYPAD_STATUS_FONT));
            mfd_keypad_assemble(keypad_data, buf);
            gr_string_size(buf, &w, &h);
            mfd_draw_font_string(buf, KEYPAD_STATUS_X - w, KEYPAD_STATUS_Y, KEYPAD_STATUS_COLOR, KEYPAD_STATUS_FONT,
                                 TRUE);
            keypad_data->last_digit = keypad_data->curr_digit;
            ResUnlock(KEYPAD_STATUS_FONT);

            // Draw buttons
            gr_set_font(ResLock(MFD_FONT));
            for (i = 0; i < NUM_KEYPAD_BUTTONS; i++) {
                ubyte clr;
                LGPoint bttn;

                bttn.x = KEYPAD_BTTNS_X +
                         (i / KEYPAD_BTTN_ROWS) * (KEYPAD_BTTNS_WD - KEYPAD_BTTN_WD) / (KEYPAD_BTTN_COLS - 1);
                bttn.y = KEYPAD_BTTNS_Y +
                         (i % KEYPAD_BTTN_ROWS) * (KEYPAD_BTTNS_HT - KEYPAD_BTTN_HT) / (KEYPAD_BTTN_ROWS - 1);

                if ((keypad_data->curr_digit > 0) &&
                    (keypad_num(i) == keypad_data->digits[keypad_data->curr_digit - 1]))
                    clr = SELECTED_ITEM_COLOR;
                else
                    clr = ITEM_COLOR;
                gr_set_fcolor((long)clr);
                ss_box(bttn.x, bttn.y, bttn.x + KEYPAD_BTTN_WD, bttn.y + KEYPAD_BTTN_HT);
                gr_set_fcolor(ITEM_COLOR + 2);
                ss_box(bttn.x - 1, bttn.y - 1, bttn.x + KEYPAD_BTTN_WD + 1, bttn.y + KEYPAD_BTTN_HT + 1);
                keypad_name(keypad_num(i), buf);
                gr_string_size(buf, &w, &h);
                mfd_draw_string(buf, bttn.x + (KEYPAD_BTTN_WD - w) / 2 + 1, bttn.y + 1, clr, TRUE);
            }
            mfd_add_rect(KEYPAD_BTTNS_X, KEYPAD_BTTNS_Y, KEYPAD_BTTNS_X + KEYPAD_BTTNS_WD,
                         KEYPAD_BTTNS_Y + KEYPAD_BTTNS_HT);
            ResUnlock(MFD_FONT);
        }
    }
    POP_CANVAS();
    mfd_update_rects(mfd);
}

/*
// --------------------
//   HUD WARE MFD
// --------------------

#define MFD_HUD_FUNC 11

#define HUD_SETTING_MASK 0xF8
#define HUD_SETTING_SHF  3

#define HUDWARE_STATUS (player_struct.hardwarez_status[CPTRIP(HUD_GOG_TRIPLE)])
#define HUDWARE_VERSION (player_struct.hardwarez[CPTRIP(HUD_GOG_TRIPLE)])


ushort hud_ware_bits[] = { HUD_COMPASS, HUD_DETECT_EXP, HUD_GRENADE };

#define NUM_HUDWARE_DISPLAYS (sizeof(hud_ware_bits)/sizeof(ushort))

#define HUDWARE_DISPLAY_AVAILABLE(dnum) (HUDWARE_VERSION & ( 1 << (dnum)))

// Note the use of negative logic here....
#define HUDWARE_DISPLAY_ACTIVE(dnum) (!(HUDWARE_STATUS & (1 << ((dnum) + HUD_SETTING_SHF))))
#define HUDWARE_DISPLAY_TOGGLE(dnum) (HUDWARE_STATUS ^= (1 << (dnum) + HUD_SETTING_SHF))

uchar mfd_hud_button_handler(MFD* m, LGPoint bttn, uiEvent* ev, void* data)
{

   // Check to see if we actually have the specified display.
   if (!HUDWARE_DISPLAY_AVAILABLE(bttn.y)) return FALSE;
   // Toggle the display
   if (ev->type == UI_EVENT_MOUSE &&  ev->subtype & (MOUSE_LDOWN|MOUSE_RDOWN))
      HUDWARE_DISPLAY_TOGGLE(bttn.y);

   if (HUDWARE_STATUS & WARE_ON) // update the actual hud.
   {
      if (HUDWARE_DISPLAY_ACTIVE(bttn.y))
         hud_set(hud_ware_bits[bttn.y]);
      else
         hud_unset(hud_ware_bits[bttn.y]);
   }
   mfd_notify_func(MFD_HUD_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, FALSE);
   return TRUE;
}


#define HUDWARE_BUTTON_X 2
#define HUDWARE_BUTTON_Y 16
#define HUDWARE_LINE_SPACING 7

#define HUDWARE_LAST_STATUS(mfd) mfd_fdata[MFD_HUD_FUNC][mfd]

#define HUDWARE_BOX_COLOR ITEM_COLOR
#define HUDWARE_BOX_SIZE  HUDWARE_LINE_SPACING

#ifdef HUDWARE_MFD

errtype mfd_hud_init(MFD_Func* f)
{
   errtype err;
   LGPoint bsize = { MFD_VIEW_WID, HUDWARE_LINE_SPACING};
   LGPoint bdims = { 1, NUM_HUDWARE_DISPLAYS };
   LGRect  brect = { { 0, HUDWARE_BUTTON_Y },
                  { MFD_VIEW_WID, HUDWARE_BUTTON_Y + NUM_HUDWARE_DISPLAYS*HUDWARE_LINE_SPACING}};
   int cnt = 0;
   err = MFDBttnArrayInit(&f->handlers[cnt++],&brect,bdims,bsize,mfd_hud_button_handler,NULL);
   if (err != OK) return err;
   f->handler_count = cnt;
   return OK;
}

void mfd_hud_expose(MFD* mfd, ubyte control)
{
   uchar full = control & MFD_EXPOSE_FULL;
   if (control == 0) return;

   mfd_clear_rects();
   PUSH_CANVAS(pmfd_canvas);
   ss_safe_set_cliprect(0,0,MFD_VIEW_WID,MFD_VIEW_HGT);

   // Lay down the "background"
   mfd_item_micro_expose(TRUE,HUD_GOG_TRIPLE);
   // clear rects so that we don't draw it if we don't have to
   if (!full) mfd_clear_rects();

   if (full || HUDWARE_STATUS != HUDWARE_LAST_STATUS(mfd->id))
   {
      int i;
      for (i = 0; i < NUM_HUDWARE_DISPLAYS; i++)
         if (HUDWARE_DISPLAY_AVAILABLE(i))
         {
            short x = HUDWARE_BUTTON_X;
            short y = HUDWARE_BUTTON_Y + i * HUDWARE_LINE_SPACING;
            short textx;
            char* s;
            // draw an "x" if active
            if (HUDWARE_DISPLAY_ACTIVE(i))
            {
               gr_set_fcolor(GOOD_RED);
               ss_int_line(x,y,x+HUDWARE_BOX_SIZE-1,y+HUDWARE_BOX_SIZE-1);
               ss_int_line(x,y+HUDWARE_BOX_SIZE-1,x+HUDWARE_BOX_SIZE-1,y);
            }
            gr_set_fcolor(HUDWARE_BOX_COLOR);
            ss_box(x,y,x+HUDWARE_BOX_SIZE,y+HUDWARE_BOX_SIZE);
            gr_set_font(ResLock(MFD_FONT));
            s = get_temp_string(REF_STR_HudBase+i);
            textx = (MFD_VIEW_WID - HUDWARE_BOX_SIZE - gr_string_width(s))/2 + HUDWARE_BOX_SIZE;
            mfd_draw_string(s,textx,y+1,gr_get_fcolor(),TRUE);
            ResUnlock(MFD_FONT);
            mfd_add_rect(x,y,MFD_VIEW_WID,y+HUDWARE_LINE_SPACING);
         }
      HUDWARE_LAST_STATUS(mfd->id) = HUDWARE_STATUS;
   }
   POP_CANVAS();
   mfd_update_rects(mfd);
}

void hudware_update_status(uchar on)
{
   int i;
   for (i = 0; i < NUM_HUDWARE_DISPLAYS; i++)
      if (HUDWARE_DISPLAY_ACTIVE(i) && on)
         hud_set(hud_ware_bits[i]);
      else
         hud_unset(hud_ware_bits[i]);
}

#endif // HUDWARE_MFD
*/

// ----------------------------------------------------------
// THE GOOFY SEVERED HEAD MFD
// ----------------------------------------------------------

void severed_head_expose(MFD *mfd, ubyte control);

void severed_head_expose(MFD *mfd, ubyte control) {
    uchar full = control & MFD_EXPOSE_FULL;
    if (full) {
        ubyte headnum = player_struct.actives[ACTIVE_GENERAL];
        ObjID head = player_struct.inventory[headnum];
        int mug;
        uint trip = ID2TRIP(head);

        if (head == OBJ_NULL || !(trip == HEAD_TRIPLE || trip == HEAD2_TRIPLE))
            return;
        // clear update rects
        mfd_clear_rects();
        // set up canvas
        gr_push_canvas(pmfd_canvas);
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
        // Clear the canvas by drawing the background bitmap
        if (!full_game_3d)
            ss_bitmap(&mfd_background, 0, 0);
        // gr_bitmap(&mfd_background, 0, 0);

        mug = REF_IMG_EmailMugShotBase + objSmallstuffs[objs[head].specID].data1;
	FrameDesc *f = RefLock(mug);
	if (f != NULL) {
	    ss_bitmap(&f->bm, (MFD_VIEW_WID - f->bm.w) / 2, (MFD_VIEW_HGT - f->bm.h) / 2);
	    RefUnlock(mug);
	} else {
	    WARN("severed_head_expose(): could not load head art ", mug);
	}

        // draw the name
        mfd_draw_string(get_object_long_name(ID2TRIP(head), NULL, 0), X_MARGIN, 2, GREEN_YELLOW_BASE, TRUE);
        mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);

        // Pop the canvas
        gr_pop_canvas();
        // Now that we've popped the canvas, we can send the
        // updated mfd to screen
        mfd_update_rects(mfd);
    }
}

// -------------------
// * GENERIC BLANK MFD
// -------------------

// ---------------------------------------------------------------------------
// mfd_expose_blank()
//
// Draw whatever we're supposed to draw if we're looking at an empty slot.

void mfd_expose_blank(MFD *m, ubyte control) {
    if (full_game_3d) {
        full_visible &= ~visible_mask(m->id);
        chg_set_sta(FULLSCREEN_UPDATE);
        return;
    }
    if ((control & MFD_EXPOSE) && !full_game_3d) {

        PUSH_CANVAS(pmfd_canvas);
        ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);

        draw_blank_mfd();

        POP_CANVAS();
        mfd_update_display(m, 0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
    }

    return;
}

// ---------------------------------------------------------------------------
//                  CALLS FROM OTHER MODULES TO THE MFD SYSTEM
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// set_inventory_mfd()
//
// Called by the inventory whenever a new drug, ware, something is clicked
// on in the inventory panel.

ulong catbasetrips[MFD_INV_CATEGORIES] = {
    0,
    MAKETRIP(CLASS_DRUG, 0, 0),
    MAKETRIP(CLASS_HARDWARE, 0, 0),
    MAKETRIP(CLASS_GRENADE, 0, 0),
    MAKETRIP(CLASS_AMMO, 0, 0),
    MAKETRIP(CLASS_GUN, 0, 0),
    0, // general inventory
    MAKETRIP(CLASS_SOFTWARE, SOFTWARE_SUBCLASS_OFFENSE, 0),
    MAKETRIP(CLASS_SOFTWARE, SOFTWARE_SUBCLASS_DEFENSE, 0),
    MAKETRIP(CLASS_SOFTWARE, SOFTWARE_SUBCLASS_ONESHOT, 0),
};

// look, another array that should not exist.
ubyte catactives[] = {
    0,
    ACTIVE_DRUG,
    ACTIVE_HARDWARE,
    ACTIVE_GRENADE,
    ACTIVE_CART,
    ACTIVE_WEAPON,
    ACTIVE_GENERAL,
    ACTIVE_COMBAT_SOFT,
    ACTIVE_DEFENSE_SOFT,
    ACTIVE_MISC_SOFT,
};

ubyte activecats[] = {
    MFD_INV_WEAPON,      MFD_INV_GRENADE,      MFD_INV_DRUG,      MFD_INV_AMMO,   MFD_INV_HARDWARE,
    MFD_INV_SOFT_COMBAT, MFD_INV_SOFT_DEFENSE, MFD_INV_SOFT_MISC, MFD_INV_GENINV, 0,
};


void set_inventory_mfd(ubyte obclass, ubyte type, uchar grab) {
    int i;
    ubyte func = MFD_EMPTY_FUNC;
    ubyte slot;
    MFD_Status stat;
    uchar classhit = FALSE;

    switch (obclass) {

    case MFD_INV_WEAPON:

        // The "grab" arg is TRUE in case blanked out by an inventory drop
        func = MFD_WEAPON_FUNC;
        slot = MFD_WEAPON_SLOT;
        stat = MFD_ACTIVE;
        if (type == MFD_INV_NOTYPE) {
            stat = MFD_EMPTY;
        }
        break;

    case MFD_INV_NULL:

        if (type == MFD_INV_NOTYPE)
            break;
        for (i = 0; i < NUM_MFDS; i++) {
            MFDSetCurrItemClass(i, obclass);
        }
        func = MFD_ITEM_FUNC;
        slot = MFD_ITEM_SLOT;
        stat = MFD_EMPTY;
        break;

    default:
        for (i = 0; i < NUM_MFDS; i++) {
            if (MFDGetCurrItemClass(i) == obclass)
                classhit = TRUE;
            if (type != MFD_INV_NOTYPE)
                MFDSetCurrItemClass(i, obclass);
        }
        slot = MFD_ITEM_SLOT;
        stat = MFD_ACTIVE;
        if (type == MFD_INV_NOTYPE) {
            if (classhit) {
                mfd_notify_func(MFD_EMPTY_FUNC, MFD_ITEM_SLOT, TRUE, MFD_EMPTY, TRUE);
                player_struct.actives[catactives[obclass]] = 0;
            }
        } else if (obclass == MFD_INV_GENINV && player_struct.inventory[type] == OBJ_NULL) {
            mfd_notify_func(MFD_EMPTY_FUNC, MFD_ITEM_SLOT, grab, MFD_EMPTY, TRUE);
        } else {
            ulong opnum = (obclass != MFD_INV_GENINV) ? OPTRIP(catbasetrips[obclass]) + type
                                                      : OPNUM(player_struct.inventory[type]);
            func = ObjProps[opnum].mfd_id;
            if (func == MFD_EMPTY_FUNC)
                func = MFD_ITEM_FUNC;
            set_current_active(catactives[obclass]);
        }
        break;
    }
    if (func != MFD_EMPTY_FUNC) {
        mfd_notify_func(func, slot, grab, stat, TRUE);
#ifdef RAISE_ON_SELECT
        if (full_game_3d) {
            int i;
            for (i = 0; i < NUM_MFDS; i++) {
                if (player_struct.mfd_current_slots[i] == MFD_ITEM_SLOT) {
#ifdef STEREO_SUPPORT
                    if (convert_use_mode == 5) {
                        full_visible = FULL_MFD_MASK(i);
                    } else
#endif
                        full_visible |= FULL_MFD_MASK(i);
                }
            }
        }
#endif
    }
    // THEN we check to see if we need to take over the info mfd

    switch (obclass) {

    case MFD_INV_HARDWARE:

        switch (type) {

        case HARDWARE_BIOWARE:

            if (WareActive(player_struct.hardwarez_status[HARDWARE_BIOWARE]))
                mfd_notify_func(MFD_BIOWARE_FUNC, MFD_INFO_SLOT, TRUE, MFD_ACTIVE, TRUE);

            break;
        }

        break;
    }

    if (obclass != MFD_INV_NULL && type != MFD_INV_NOTYPE)
        player_struct.actives[catactives[obclass]] = type;
}

void update_item_mfd(void) {
    ubyte curr = player_struct.current_active;
    if (curr != NULL_ACTIVE) {
        ubyte obclass = activecats[curr];
        ubyte type = player_struct.actives[curr];
        ulong opnum =
            (obclass != MFD_INV_GENINV) ? OPTRIP(catbasetrips[obclass]) + type : OPNUM(player_struct.inventory[type]);
        int func = ObjProps[opnum].mfd_id;
        if (func != MFD_EMPTY_FUNC) {
            mfd_notify_func(func, MFD_ITEM_FUNC, TRUE, MFD_ACTIVE, TRUE);
        }
    }
}

uchar mfd_distance_remove(ubyte slot_func) {
    switch (slot_func) {
    case MFD_KEYPAD_FUNC:
    case MFD_FIXTURE_FUNC:
    case MFD_ELEV_FUNC:
    case MFD_BARK_FUNC:
    case MFD_ACCESSPANEL_FUNC:
    case MFD_GUMP_FUNC:
    case MFD_GRIDPANEL_FUNC:
        return TRUE;
    }
    return FALSE;
}

// -------
// DEFAULT MFD FUNC QUALIFYING FUNCTIONS
uchar mfd_target_qual(void) { return (player_struct.hardwarez[HARDWARE_TARGET] > 0); }

uchar mfd_automap_qual(void) { return (player_struct.hardwarez[HARDWARE_AUTOMAP] > 0); }

uchar mfd_weapon_qual(void) {
    return (player_struct.weapons[player_struct.actives[ACTIVE_WEAPON]].type != EMPTY_WEAPON_SLOT);
}

#define PANEL_PRIORITY 37

MFD_Func mfd_funcs[MFD_NUM_FUNCS] = {
    // MFD_EMPTY_FUNC   0
    {mfd_expose_blank, NULL, NULL, 255, MFD_NOSAVEREST},
    // MFD_ITEM_FUNC    1
    {mfd_item_expose, mfd_item_handler, mfd_item_init, 40},
    // MFD_MAP_FUNC     2
    {mfd_map_expose, mfd_map_handler, mfd_map_init, 20, MFD_INCREMENTAL},
    // MFD_TARGET_FUNC  3
    {mfd_target_expose, mfd_target_handler, NULL, 21},
    // MFD_ANIM_FUNC    4
    {mfd_expose_blank, NULL, NULL, 255},
    // MFD_WEAPON_FUNC  5
    {mfd_weapon_expose, mfd_weapon_handler, mfd_weapon_init, 25},
    // MFD_BIOWARE_FUNC 6
    {mfd_bioware_expose, NULL, NULL, 50, MFD_NOSAVEREST},
    // MFD_LANTERN_FUNC 7
    {mfd_lanternware_expose, NULL, mfd_lanternware_init, 38},
    // MFD_3DVIEW_FUNC  8
    {mfd_view360_expose, NULL, NULL, 25, MFD_NOSAVEREST},
    // MFD_ELEV_FUNC    9
    {mfd_elevator_expose, NULL, mfd_elevator_init, PANEL_PRIORITY, MFD_NOSAVEREST},
    // MFD_GRENADE_FUNC 10
    {mfd_grenade_expose, mfd_grenade_handler, mfd_grenade_init, 32},
    // MFD_HUD_FUNC     11
    {
        mfd_expose_blank,
    },
    // MFD_FIXTURE_FUNC 12
    {mfd_fixture_expose, mfd_fixture_handler, NULL, 32, MFD_NOSAVEREST},
    // MFD_KEYPAD_FUNC  13
    {mfd_keypad_expose, mfd_keypad_handler, mfd_keypad_init, PANEL_PRIORITY, MFD_NOSAVEREST},
    // MFD_EMAILMUG_FUNC 14
    {mfd_emailmug_expose, mfd_emailmug_handler, NULL, 60, MFD_NOSAVEREST},
    // MFD_EMAILWARE_FUNC 15
    {mfd_emailware_expose, NULL, mfd_emailware_init, 60},
    // MFD_PLOTWARE_FUNC  16
    {mfd_plotware_expose, NULL, mfd_plotware_init, 55},
    // MFD_BARK_FUNC 17
    {mfd_bark_expose, NULL, NULL, 255, MFD_NOSAVEREST},
    // MFD_ACCESSPANEL_FUNC 18
    {mfd_accesspanel_expose, mfd_accesspanel_handler, mfd_accesspanel_init, PANEL_PRIORITY,
     MFD_INCREMENTAL | MFD_NOSAVEREST},
    // MFD_SHIELD_FUNC 19
    {mfd_shieldware_expose, mfd_shield_handler, mfd_shield_init, 36},
    // MFD_MOTION_FUNC 20
    {mfd_motionware_expose, NULL, mfd_motion_init, 36},
    // MFD_SEVERED_HEAD_FUNC 21
    {severed_head_expose, NULL, NULL, 250},
    // MFD_TARGETWARE_FUNC 22
    {mfd_targetware_expose, mfd_targetware_handler, NULL, 40},
    // MFD_GUMP_FUNC 23
    {mfd_gump_expose, mfd_gump_handler, NULL, 40, MFD_NOSAVEREST},
    // MFD_CARD_FUNC 24
    {mfd_accesscard_expose, NULL, NULL, 40},
    // MFD_BIOHELP_FUNC 25
    {mfd_biohelp_expose, mfd_biohelp_handler, mfd_biohelp_init, 40},
    // MFD_GRIDPANEL_FUNC 26
    {mfd_gridpanel_expose, mfd_gridpanel_handler, mfd_gridpanel_init, PANEL_PRIORITY, MFD_NOSAVEREST},
    // MFD_GAMES_FUNC 27
    {mfd_games_expose, mfd_games_handler, mfd_games_init, PANEL_PRIORITY, MFD_INCREMENTAL | MFD_NOSAVEREST},
    // MFD_CYBERSPACE_FUNC 28
    {mfd_cspace_expose, NULL, NULL, 40},
    // MFD_VIEWHELP_FUNC 29
    {mfd_viewhelp_expose, NULL, mfd_viewhelp_init, 40},
    // MFD_GEAR_FUNC 30
    {mfd_gear_expose, mfd_gear_handler, NULL, 40}};
